Xwork's Blog

The lord is waiting to take your hand.

Mostrando las entradas con la etiqueta aprende. Mostrar todas las entradas
Mostrando las entradas con la etiqueta aprende. Mostrar todas las entradas

Vamos a programar #107 - Agregando imagenes a archivos FLAC usando C# (Parte 1).

Hola de nuevo a todos. El día de hoy vamos a continuar con mas de los archivos FLAC. En post anteriores vimos cómo insertar la imagen usando solo un editor hexadecimal, pero hoy, vamos a usar C# para crear un programa que lo haga por nosotros.


 Antes de continuar te recomiendo que leas los post relacionados:

 Una vez leidos los post anteriores, vamos a ver el código que hace funcionar el programa. Algo importante que debo aclarar, es que por primera vez en la historia del blog, voy a usar visual studio 2022 y .Net 8.0, la mayor parte del código es retro-compatible, pero en c# 7.0 se agregaron algunas caracteristicas que hacen las cosas mas sencillas, por lo que si quieres probar el código deberías de instalar al menos esa version de visual studio.Además el código es mas que nada una prueba de concepto por lo que se puede optimizar, pero además, todo se hace para que sea lo mas similar al proceso que se hizo a mano (Vamos a programar #102) por lo que si quieres optimizarlo, puedes hacerlo sin ningun problema.

 

Una vez dicho eso,  el código es el siguiente:

using System.Diagnostics.Eventing.Reader;
using System.Runtime.InteropServices;
using System.Text;
using System.Drawing.Imaging;
using System.Net.Http.Headers;
using System.ComponentModel;

namespace FlacPictureWriter
{
public partial class Form1 : Form

{

///Picture type:
/// $00 Other
/// $01 32x32 pixels 'file icon' (PNG only)
/// $02 Other file icon
/// $03 Cover (front)
/// $04 Cover (back)
/// $05 Leaflet page
/// $06 Media (e.g. lable side of CD)
/// $07 Lead artist/lead performer/soloist
/// $08 Artist/performer
/// $09 Conductor
/// $0A Band/Orchestra
/// $0B Composer
/// $0C Lyricist/text writer
/// $0D Recording Location
/// $0E During recording
/// $0F During performance
/// $10 Movie/video screen capture
/// $11 A bright coloured fish
/// $12 Illustration
/// $13 Band/artist logotype
/// $14 Publisher/Studio logotype
private enum APICType
{
/// <summary>
/// Imagen génerica
/// </summary>
APIC_TYPE_Other = 0,
/// <summary>
/// ícono (solo PNG)
/// </summary>
APIC_TYPE_FileIcon,
/// <summary>
/// Otro ícono
/// </summary>
APIC_TYPE_OtherFile_Icon,
/// <summary>
/// Cubierta frontal del disco
/// </summary>
APIC_TYPE_CoverFront,
/// <summary>
/// Cubierta trasera del disco
/// </summary>
APIC_TYPE_CoverBack,
/// <summary>
/// Folleto
/// </summary>
APIC_TYPE_LeafletPage,
/// <summary>
/// Otra ímagen
/// </summary>
APIC_TYPE_Media,
/// <summary>
/// Artista principal
/// </summary>
APIC_TYPE_LeadArtist,
/// <summary>
/// Artista
/// </summary>
APIC_TYPE_Artist,
/// <summary>
/// Conductor
/// </summary>
APIC_TYPE_Conductor,
/// <summary>
/// Banda
/// </summary>
APIC_TYPE_Band,
/// <summary>
/// Compositor
/// </summary>
APIC_TYPE_Composer,
/// <summary>
/// Letrista
/// </summary>
APIC_TYPE_Lyricist,
/// <summary>
/// Lugar de la grabacion
/// </summary>
APIC_TYPE_RecordingLocation,
/// <summary>
/// Captura durante la grabacion
/// </summary>
APIC_TYPE_DuringRecording,
/// <summary>
/// Captura durante concierto
/// </summary>
APIC_TYPE_DuringPerformance,
/// <summary>
/// Captura de videclip
/// </summary>
APIC_TYPE_VideoScreenCapture,
/// <summary>
/// ???
/// </summary>
APIC_TYPE_ABrightColouredFish,
/// <summary>
/// Ilustracion
/// </summary>
APIC_TYPE_Illustration,
/// <summary>
/// Logotipo de la banda/artista
/// </summary>
APIC_TYPE_ArtistLogotype,
/// <summary>
/// Logotipo de la discografica
/// </summary>
APIC_TYPE_PublisherLogotype,
APIC_TYPE_NoValid

}


// 0 : STREAMINFO 0000000
// 1 : PADDING 0000001
// 2 : APPLICATION 0000010
// 3 : SEEKTABLE 0000011
// 4 : VORBIS_COMMENT 0000100
// 5 : CUESHEET 0000101
// 6 : PICTURE 0000110
// 7-126 : reserved 1111000 - Invalid
// 127 : invalid, to avoid confusion with a frame sync code
/// <summary>
/// Enumeración con los posibles bloques contenidos en un archivo FLAC
/// </summary>
private enum BlocksTypes
{
/// <summary>
/// Bloque mandatorio con la información del Stream
/// </summary>
Block_Type_StreamInfo = 0,
/// <summary>
/// Bloque de Padding
/// </summary>
Block_Type_Padding = 1,
/// <summary>
/// Bloque con la informacion de la aplicación
/// </summary>
Block_Type_Application = 2,
/// <summary>
/// Bloque Seektable
/// </summary>
Block_Type_SeekTable = 3,
/// <summary>
/// Bloque con metadatos
/// </summary>
Block_Type_VorbisComment = 4,
/// <summary>
/// Bloque con cuesheet
/// </summary>
Block_Type_CueSheet = 5,
/// <summary>
/// Bloque con imagen
/// </summary>
Block_Type_Picture = 6,
/// <summary>
/// Bloque no válido
/// </summary>
Block_Type_NoValid = 7

}
public Form1()
{
InitializeComponent();
}


List<UInt32> Direcciones;
bool TieneImagen = false;
UInt32 ImageSize = 0;
UInt32 LastBLockPosition = 0;
UInt32 VorbisCommentPosition = 0;
UInt32 ImagePosition = 0;

private byte[] NormalizeToFourBytes(UInt32 Value, bool Reverse = false)
{
byte[] buff = BitConverter.GetBytes(Value);

if (Reverse)
Array.Reverse(buff);

return buff;
}
/// <summary>
/// Obtiene el "Mime Type" basado en la extension de un archivo
/// </summary>
/// <param name="FilePath">Ruta de la cual se va a extraer la información</param>
/// <returns></returns>
private string GetMIMEType(string FilePath)
{

FileInfo FI = new(FilePath);
if (string.Compare(FI.Extension, ".jpg", StringComparison.OrdinalIgnoreCase) == 0)
{
return @"image/jpeg";
}
else if (string.Compare(FI.Extension, ".png", StringComparison.OrdinalIgnoreCase) == 0)
{
return @"image/PNG";
}
else
{
return "No valido";
}
}


/// <summary>
/// Crea una imagen a partir de una secuencia de bytes
/// </summary>
/// <param name="bytesArr">Matriz de bytes que contiene los datos de la imagen</param>
/// <returns>Regresa una imagen</returns>
public Image ByteArrayToImage(byte[] bytesArr)
{
using (MemoryStream memstr = new(bytesArr))
{
Image img = Image.FromStream(memstr);
return img;
}
}

// 4 4 n*4 4 n*4 4 4 4 4 !4 !n*4
//<32>,<32>,<n*8>,<32>,<n*8>,<32>,<32>,<32>,<32>,<32>,<n*8>
// 1 2 3 4 5 6 7 8 9 10 11
/// <summary>
/// Lee todo los datos del bloque PICTURE y extrae la imagen contenida
/// </summary>
/// <param name="TheData">Arreglos bytes que contiene todo el bloque PICTURE</param>
/// <returns>Regresa una imagen</returns>
private Image ReadPictureData(byte[] TheData)
{

uint CurrentPos = 4;//1
uint CurrentSize = GetBlockSize(TheData, (int)CurrentPos, 4);
CurrentPos += 4;//2
string MimeType = Encoding.ASCII.GetString(TheData, (int)CurrentPos, (int)CurrentSize);

CurrentPos += CurrentSize;//3

CurrentSize = GetBlockSize(TheData, (int)CurrentPos, 4);
CurrentPos += (CurrentSize + (4 * 5));//4-9

CurrentSize = GetBlockSize(TheData, (int)CurrentPos, 4);
CurrentPos += 4;

byte[] TempImage = new byte[CurrentSize];
Array.Copy(TheData, CurrentPos, TempImage, 0, CurrentSize);
return ByteArrayToImage(TempImage);


}

/// <summary>
/// Actualiza la imagen de un archivo FLAC.
/// </summary>
/// <param name="ImagePath">Ruta de la imagen JPG</param>
private byte[] BuildAPICFrame(string ImagePath, int PictureType, string MimeType, UInt32 ImageSize)
{

List<byte> Data = new List<byte>();
byte[] Buff = new byte[ImageSize];

using (FileStream FS = new FileStream(ImagePath, FileMode.Open))
{
using (BinaryReader BR = new(FS))
{
Buff = BR.ReadBytes((int)ImageSize);
}
}

//Tipo de Imagen APIC
Data.AddRange(NormalizeToFourBytes((UInt32)PictureType, true));
//Tamaño del tipo de contenido
Data.AddRange(NormalizeToFourBytes((UInt32)MimeType.Length, true));
//Tipo de contenido
Data.AddRange(Encoding.ASCII.GetBytes(MimeType));
//Descripcion del tipo de contenido
Data.AddRange(NormalizeToFourBytes((UInt32)MimeType.Length, true));
//Tamaño de la descripcion del contenido
Data.AddRange(Encoding.ASCII.GetBytes(MimeType));
//Ancho de la imagen
Data.AddRange(NormalizeToFourBytes(0, true));
//Alto de la imagen
Data.AddRange(NormalizeToFourBytes(0, true));
//Profundidad del color
Data.AddRange(NormalizeToFourBytes(0, true));
//Indice de la paleta de colores
Data.AddRange(NormalizeToFourBytes(0, true));
//Tamaño de la imagen en bytes
Data.AddRange(NormalizeToFourBytes(ImageSize, true));
//Datos de la Imagen
Data.AddRange(Buff);



return Data.ToArray();
}
/// <summary>
/// Construye el bloque de imagen
/// </summary>
/// <param name="APICData"></param>
/// <returns></returns>
private byte[] BuildPictureBlock(byte[] APICData)
{
byte[] Buff = new byte[APICData.Length + 4];
byte[] PICSize = new byte[4];

PICSize = NormalizeToFourBytes((UInt32)APICData.Length, true);
PICSize.CopyTo(Buff, 0);


//siempre pondremos la imagen al final de los metadatos
//Establecemos el marcador de ultimo bloque y el tipo de bloque en 6 (Block_Type_Picture)
//Quedando 10000110b o 134
Buff[0] = 134;
APICData.CopyTo(Buff, 4);

return Buff;
}


private List<UInt32> GetOffsets(string FileName)
{
byte[] DataBuff;
List<UInt32> offsets = new List<UInt32>();
bool LastFrame = false;


using (FileStream FS = new(FileName, FileMode.Open, FileAccess.Read, FileShare.Read))
{
offsets.Add((UInt32)FS.Position);
using (BinaryReader BR = new(FS, Encoding.ASCII))
{
DataBuff = BR.ReadBytes(4);
if (string.Equals(Encoding.ASCII.GetString(DataBuff), "fLaC", StringComparison.Ordinal))
{
UInt32 CurrentBlockSize;
UInt32 CurrentPosition = (UInt32)FS.Position;
BlocksTypes CurrentBlockType = NumberToBlockType(GetBlockType(DataBuff[0]));


while (!LastFrame)
{
//primer bloque siempre ser� Block_Type_StreamInfo


offsets.Add((UInt32)FS.Position);
DataBuff = BR.ReadBytes(4);
LastFrame = IsLastFrame(DataBuff[0]);
CurrentBlockType = NumberToBlockType(GetBlockType(DataBuff[0]));
CurrentBlockSize = GetBlockSize(DataBuff);
FS.Seek(FS.Position + CurrentBlockSize, SeekOrigin.Begin);


if (CurrentBlockType == BlocksTypes.Block_Type_VorbisComment)
{
VorbisCommentPosition = offsets[offsets.Count - 1];
}


if (CurrentBlockType == BlocksTypes.Block_Type_Picture)
{
TieneImagen = true;
ImagePosition = offsets[offsets.Count - 1];
}


if (LastFrame)
LastBLockPosition = offsets[offsets.Count - 1];
}
}
else
{
MessageBox.Show("Archivo Flac no v�lido");
}


}
}


return offsets;
}


/// <summary>
/// Construye el encabezado de un bloque de informacion
/// </summary>
/// <param name="Number">Valor que indica la longitud del bloque en bytes</param>
/// <param name="BlockType">Valor que indica el tipo de bloque de acuerdo a <c>BlockTypes</c></param>
/// <param name="IsLastFrame">Indica si es el ultimo bloque de informacion</param>
/// <returns>Regresa un arreglo de cuatro bytes que representan el encabezado del bloque</returns>
private byte[] BuildHeader(uint Number, BlocksTypes BlockType, bool IsLastFrame)
{
byte[] HeaderBuff = new byte[4];

HeaderBuff = BitConverter.GetBytes(Number);



HeaderBuff[3] = (byte)BlockType;
if (IsLastFrame)
{
HeaderBuff[3] |= 128;
}

Array.Reverse(HeaderBuff);

return HeaderBuff;
}
/// <summary>
/// Convierte un número en su valor equivalente al tipo de bloque
/// </summary>
/// <param name="Data">
/// Entero sin signo con la representacion del bloque</param>
/// <returns>Regresa un valor de la enumeración <typeparamref name="BlockTypes"/>BlockTypes</returns>
private BlocksTypes NumberToBlockType(uint Data)
{
switch (Data)
{
case 0:
return BlocksTypes.Block_Type_StreamInfo;
case 1:
return BlocksTypes.Block_Type_Padding;
case 2:
return BlocksTypes.Block_Type_Application;
case 3:
return BlocksTypes.Block_Type_SeekTable;
case 4:
return BlocksTypes.Block_Type_VorbisComment;
case 5:
return BlocksTypes.Block_Type_CueSheet;
case 6:
return BlocksTypes.Block_Type_Picture;
default:
return BlocksTypes.Block_Type_NoValid;
}
}

/// <summary>
/// Lee el bloque con la informacion estructurada
/// </summary>
/// <param name="TheData">Arreglo de bytes del cual se extraera la información</param>
/// <returns>Regresa un arreglo del tipo string con todos los campos que se encontraron</returns>
private string[] ReadVorbisData(byte[] TheData)
{

uint NumberOfFields = 0;
uint CurrentField = 1;
uint CurrentPosition = 0;
//Vendor
uint CurrentSize = BitConverter.ToUInt32(TheData, (int)CurrentPosition);
CurrentPosition += 4;
byte[] CurrentChunk = new byte[CurrentSize];
Array.Copy(TheData, CurrentPosition, CurrentChunk, 0, CurrentSize);
string Currenttext = Encoding.UTF8.GetString(CurrentChunk);

CurrentPosition += CurrentSize;
NumberOfFields = BitConverter.ToUInt32(TheData, (int)CurrentPosition);
CurrentPosition += 4;

string[] Fields = new string[NumberOfFields];
//CommentField
while (CurrentField <= NumberOfFields)
{
CurrentSize = BitConverter.ToUInt32(TheData, (int)CurrentPosition);
CurrentPosition += 4;
byte[] CurrenUserCommentList = new byte[CurrentSize];
Array.Copy(TheData, CurrentPosition, CurrenUserCommentList, 0, CurrentSize);
Currenttext = Encoding.UTF8.GetString(CurrenUserCommentList);
CurrentPosition += CurrentSize;
Fields[CurrentField - 1] = Currenttext;
CurrentField += 1;
}

return Fields;
}

/// <summary>
/// Obtiene el valor del bloque desde un byte
/// </summary>
/// <param name="Data">byte del cual se va a obtener la información</param>
/// <returns>Regresa un valor que es equivalente al tipo de bloque</returns>
private uint GetBlockType(byte Data)
{
Data <<= 3;
Data >>= 3;

return Data;
}

/// <summary>
/// Obtiene si el bloque es el último de la serie
/// </summary>
/// <param name="Data">byte del cual se obtendra la información</param>
/// <returns>true si el bloque es el último, false en caso contrario</returns>
private bool IsLastFrame(byte Data)
{
Data >>= 7;

if (Data == 1)
return true;
else
return false;
}

/// <summary>
/// Obtiene el tamaño de un bloque
/// </summary>
/// <param name="Data">Arreglo de bytes que contiene el tamaño del bloque</param>
/// <returns>Regresa el tamaño del bloque actual</returns>
private uint GetBlockSize(byte[] Data)
{

byte[] CurrentData = Data;
//Ponemos el primer byte en 0 porque se usa para identificar el bloque
//a la hora de convertir a UInt32 se esperan 4 bytes, pero este siempre será 0 u otro valor no relevante
//para el tamaño del bloque
CurrentData[0] = 0;
Array.Reverse(CurrentData);

return BitConverter.ToUInt32(CurrentData, 0);
}

/// <summary>
/// Obtiene el tamaño de un bloque
/// </summary>
/// <param name="Data">Arreglo de bytes que contiene el tamaño del bloque</param>
/// <param name="StartIndex">Indica la posicion en donde se empezará a leer</param>
/// <param name="Size">Indica el tamaño en bytes que se van a leer</param>
/// <returns>Regresa el tamaño del bloque actual</returns>

private uint GetBlockSize(byte[] Data, int StartIndex, int Size)
{
byte[] CurrentData = new byte[4];
Array.Copy(Data, StartIndex, CurrentData, 0, Size);
Array.Reverse(CurrentData);

return BitConverter.ToUInt32(CurrentData, 0);
}

/// <summary>
/// Obtiene una imagen desde un archivo
/// </summary>
private void GetImageFromFile()
{

using OpenFileDialog OpDiag = new()
{
Filter = "Imagenes|*.jpg;*.png|Archivos JPG|*.jpg|Imagenes PNG|*.png",
Multiselect = false
};

if (OpDiag.ShowDialog() == DialogResult.OK)
{
TxtInputPic.Text = OpDiag.FileName;
PicInput.ImageLocation = TxtInputPic.Text;
PicInput.SizeMode = PictureBoxSizeMode.StretchImage;
FileInfo FileSize = new FileInfo(TxtInputPic.Text);
ImageSize = (UInt32)FileSize.Length;
LblPicInfo.Text = "Tamaño de la imagen en bytes " + ImageSize.ToString() + " bytes";
}
}

/// <summary>
/// Obtiene un bloque de datos de un archivo a partir de una posicion definida
/// </summary>
/// <param name="FileName">Archivo del cual se va a extraer el bloque</param>
/// <param name="StartPosition">Posicion des la cual se va a obtener el bloque relativo al inicio del archivo</param>
/// <returns>Regresa un arreglo de bytes con el bloque completo</returns>
private byte[] GetBlockData(string FileName, int StartPosition)
{
byte[] DataBuff;
using (FileStream FS = new(FileName, FileMode.Open, FileAccess.Read, FileShare.Read))
{
using (BinaryReader BR = new(FS, Encoding.ASCII))
{
UInt32 CurrentBlockSize;
UInt32 CurrentPosition = (UInt32)FS.Position;


FS.Seek(StartPosition, SeekOrigin.Begin);
DataBuff = BR.ReadBytes(4);

CurrentBlockSize = GetBlockSize(DataBuff);
FS.Seek(StartPosition, SeekOrigin.Begin);

return (BR.ReadBytes((int)(CurrentBlockSize + 4)));
}
}
}

/// <summary>
/// Obtiene los datos de un bloque
/// </summary>
/// <param name="FileName">Nombre del archivo del cual se obtendra</param>
/// <param name="TypeOfBlock">Tipo de bloque que se va a buscar</param>
private void GetBlock(string FileName, BlocksTypes TypeOfBlock, int StartPosition)
{
byte[] DataBuff;
using (FileStream FS = new(FileName, FileMode.Open, FileAccess.Read, FileShare.Read))
{
using (BinaryReader BR = new(FS, Encoding.ASCII))
{
UInt32 CurrentBlockSize;
UInt32 CurrentPosition = (UInt32)FS.Position;
BlocksTypes CurrentBlockType = BlocksTypes.Block_Type_NoValid;


FS.Seek(StartPosition, SeekOrigin.Begin);
DataBuff = BR.ReadBytes(4);


CurrentBlockType = NumberToBlockType(GetBlockType(DataBuff[0]));


if (CurrentBlockType == TypeOfBlock)
{
if (TypeOfBlock == BlocksTypes.Block_Type_VorbisComment)
{
string[] Fields;
CurrentBlockSize = GetBlockSize(DataBuff);
Fields = ReadVorbisData(BR.ReadBytes((int)CurrentBlockSize));
TxtInfo.Clear();


for (int i = 0; i < Fields.Length; i++)
{
TxtInfo.Text += Fields[i] + " | ";
}
}
else if (TypeOfBlock == BlocksTypes.Block_Type_Picture)
{


CurrentBlockSize = GetBlockSize(DataBuff);
PicFlac.Image = ReadPictureData(BR.ReadBytes((int)CurrentBlockSize));
}
}
else
{
return;
}
}
}
}


private void BtnOpen_Click(object sender, EventArgs e)
{
TieneImagen = false;
VorbisCommentPosition = 0;
ImagePosition = 0;

OpenFileDialog OpDiag = new()
{
Filter = "Archivos Flac|*.flac|Todos los arvhivos|*.*",
Multiselect = false
};

if (OpDiag.ShowDialog() == DialogResult.OK)
{
txtInputFlac.Text = OpDiag.FileName;
PicFlac.SizeMode = PictureBoxSizeMode.StretchImage;
Direcciones = GetOffsets(txtInputFlac.Text);

GetBlock(txtInputFlac.Text, BlocksTypes.Block_Type_VorbisComment, (int)VorbisCommentPosition);

if (TieneImagen)
GetBlock(txtInputFlac.Text, BlocksTypes.Block_Type_Picture, (int)ImagePosition);
}
}

private void BtnOpenPic_Click(object sender, EventArgs e)
{
ImageSize = 0;
GetImageFromFile();
}

private void BtnUpdateImage_Click(object sender, EventArgs e)
{
if (string.IsNullOrEmpty(txtInputFlac.Text))
{
MessageBox.Show("No hay arhivo de entrada", "Flac Image Writer", MessageBoxButtons.OK, MessageBoxIcon.Error);
return;
}else if (string.IsNullOrEmpty(TxtInputPic.Text))
{
MessageBox.Show("No hay imagen de entrada", "Flac Image Writer", MessageBoxButtons.OK, MessageBoxIcon.Error);
return;
}

string SaveFile;
bool HasImage = false;
List<UInt32> Direcciones = GetOffsets(txtInputFlac.Text);
int CurrentSize = (int)Direcciones[Direcciones.Count - 1];
int bufferSize = 1024 * 1024;

using (SaveFileDialog SavDiag = new SaveFileDialog())
{
SavDiag.Filter = "Archivos FLAC|*.flac";
SavDiag.AddExtension = true;
SavDiag.OverwritePrompt = true;
if (SavDiag.ShowDialog() == DialogResult.OK)
SaveFile = SavDiag.FileName;
else
return;
}

using (FileStream FSWrite = new FileStream(SaveFile, FileMode.Create, FileAccess.Write, FileShare.ReadWrite))
{
using (BinaryWriter BW = new(FSWrite))
{

BW.Write(Encoding.ASCII.GetBytes("fLaC"));

for (int x = 1; x < Direcciones.Count; x++)
{
byte[] FileBuff = GetBlockData(txtInputFlac.Text, (int)Direcciones[x]);

BlocksTypes CurrentBlockType = BlocksTypes.Block_Type_NoValid;
CurrentBlockType = NumberToBlockType(GetBlockType(FileBuff[0]));

if (CurrentBlockType == BlocksTypes.Block_Type_Picture)
HasImage = true;


if (x == Direcciones.Count - 1)
{
if (!HasImage)
{
CurrentSize += FileBuff.Length;
BW.Write(BuildHeader((uint)(FileBuff.Length - 4), CurrentBlockType, false));
}
else
{
CurrentSize += FileBuff.Length;
BW.Write(BuildHeader((uint)(FileBuff.Length - 4), CurrentBlockType, true));
}

}
else
{

BW.Write(BuildHeader((uint)(FileBuff.Length - 4), CurrentBlockType, false));
}

BW.Write(FileBuff, 4, FileBuff.Length - 4);

}

if (HasImage == false)
BW.Write(BuildPictureBlock(BuildAPICFrame(TxtInputPic.Text, CboPictureType.SelectedIndex, GetMIMEType(TxtInputPic.Text), ImageSize)));



FileStream fs = new FileStream(txtInputFlac.Text, FileMode.Open, FileAccess.ReadWrite);

fs.Position = CurrentSize;
int bytesRead = -1;
byte[] bytes = new byte[bufferSize];

while ((bytesRead = fs.Read(bytes, 0, bufferSize)) > 0)
{
BW.Write(bytes, 0, bytesRead);
}

fs.Close();

}
}
}

private void Form1_Load(object sender, EventArgs e)
{
CboPictureType.SelectedIndex = 3;
}

private void PicFlac_DoubleClick(object sender, EventArgs e)
{
try
{


if (TieneImagen)
{
using (SaveFileDialog SavDiag = new SaveFileDialog())
{
SavDiag.Filter = "Arhivo Jpeg|*.jpg";

if (SavDiag.ShowDialog() == DialogResult.OK)
PicFlac.Image.Save(SavDiag.FileName);
}
//PicFlac.Image.Save
}
}
catch
{
MessageBox.Show("Error al guardar la imagen","Error",MessageBoxButtons.OK,MessageBoxIcon.Error);
}

}


private void button1_Click(object sender, EventArgs e)
{
}
}
}

 

Ahora veamos las funciones. 

Primero empezamos con la enumeración "APICType", en ella se definen los posibles valores para el tipo de imagen que se va incrustarde acuerdo a lo establecido en la documentacion. Esto solo se hace para poder identificar las cosas de la forma mas humana posible, no es del todo necesario, pero ayuda bastante.

La primera funcion es  "NormalizeToFourBytes()". Esta función recibe dos parámetros, el primero, es un valor del tipo UInt32 llamado "Value" y el otro es un valor del tipo bool llamado "Reverse". Esta función se encarga de convertir un numero en su equivalente en una secuencia de bytes, pero además tiene la opción de invertir el orden de la secuencia de bytes, dependiendo de la maquina en la que se trabaje o del idioma, los números se pueden representar de forma distinta, por ejemplo, si tenemos el número hexadecimal 0x3259 , puede representarse cómo 0x32 0x59 o 0x59 0x32. La función devuelve un arreglo de bytes con la representación del número.

La siguiente función es "GetMIMEType()". Esta función recibe un valor del tipo string con una cadena de texto. Se espera solo la extensión de las imágenes  compatibles, es decir solo .jpg o .png. Regresa el Mime Type basado en la cadena de texto.

La siguiente funcion es "ByteArrayToImage()" Esta funcion recibe un arreglo de bytes, se espera que la secuencia de bytes en si conformen una imagen. Regresa un valor del tipo Image que contiene la imagen de la secuencia de bytes.

Y bien, por ahora es todo, las siguientes funciones merecen una explicacion mas detallada para poder entender mejor cada una. el código del formulario principal lo puedes copiar y pegar para probarlo, pero al final cuando todo este listo lo publicaré en mi dropbox para que lo puedas descargar y probar.

Los leo luego 

 

Vamos a programar #102 - Leyendo metadatos de archivos FLAC (pt.1)

 Hola de nuevo a todos. El día de hoy vamos a continuar con más de los archivos FLAC.


En los post mas recientes hemos visto las cualidades de los archivos FLAC, pero además cómo crear los nuestros. Al igual que en MP3, existen metadatos embebidos en ellos, y sirven para describir algunos datos importantes tales cómo: el nombre de la canción, el artista, el nombre del álbum, el genero, etc.


Estructura de un archivo FLAC

La estructura de los archivos FLAC es totalmente diferente a la de un MP3 por lo que vale la pena revisarla por separado. Cada archivo FLAC consiste de cuatro partes principales:

  • Una cadena de cuatro bytes con la secuencia "fLaC" (así tal cual se ve).
  • Un bloque de metadatos con la información del "stream"
  • Cero o mas bloques de metadatos.
  • Uno o mas "frames" de audio.

Para mas detalles se puede revisar la página de xiph.org, donde esta la descripción completa para los archivos FLAC.

De las cuatro partes que se mencionan anteriormente, nos concentraremos en la segunda y tercera (Técnicamente las dos entran en el mismo conjunto, solo que la segunda es obligatoria, mientras que las subsecuentes son opcionales, hablando solamente de los bloques de metadatos), por lo que revisaremos cómo es que está compuesta cada una.

Pero antes una representación de cómo está estructurado el archivo:


Si observamos bien la imagen, veremos que hay dos partes centrales: "STREAM" y "FRAME", por ahora revisaremos la parte de "STREAM" que contiene los datos que nos interesan. 

Metadatos

A partir de ahora nos referiremos a cada parte cómo "bloque". Del lado de METADATA, cada bloque estará constituido por un encabezado (HEADER) y el bloque de datos en si, el encabezado se encarga de describir cómo es que cada parte esta formada. Para poder leer cada bloque, deberemos de seguir los datos de este. Salvo que se indique lo contrario, cada valor N representará un solo bit (OJO: bit, no byte al menos que se indique lo contrario), al igual que en la documentación (que recomiendo encarecidamente leer dos veces), usaremos un número entre los corchetes cuadrados "<" y ">" El valor que se encuentre en medio dirá cuantos bits conforman esa parte. Habiendo aclarado lo anterior, veamos cómo se forma el encabezado de cada bloque:
  • STREAM
  • METADATA_BLOCK
  • METADATA_BLOCK_HEADER (Encabezado)
    • <1>   Indicador de último bloque
    • <7>   Descriptor del tipo de bloque
    • <24> Tamaño del bloque en bytes (BYTES!!!)
Para el indicador del último bloque se usa un solo bit, si es 0 indica que hay mas bloques a continuación, pero si es 1, indica que el bloque es el último y empiezan los "FRAMES" por lo que si solo estamos leyendo los metadatos, deberíamos de dejar de buscar.

La secuencia de los siguientes 7 bits, indican que tipo de bloque es el que sigue a continuación, para ello hay una serie ya predefinida y dependiendo del valor que este nos diga, el bloque del tipo será. Actualmente solo hay siete tipos de bloque ya predefinidos y son los que siguen:
  • 0: STREAMINFO
    • Contiene información acerca de todo el archivo, este bloque es mandatorio y siempre será e primero de la secuencia después de la secuencia 0x66, 0x4c, 0x61, 0x43 (o de manera simple después de  "fLaC" ).
  • 1: PADDING
    • Este tipo de bloque sirve cómo reserva y el contenido no representará nada, normalmente se llena con 0x00. Cuando se crea el archivo sin los metadatos completos, en lugar des escribir un archivo nuevo con los bloques nuevos, simplemente se reemplaza este bloque conservando el archivo original y agilizando la escritura del mismo.
  • 2: APPLICATION
    • Nombre de la aplicación que se uso para codificar el archivo, puede ser cualquiera que el usuario elija, aunque se recomienda el registro de la aplicación para obtener un ID único para cada quien.
  • 3: SEEKTABLE
    • Sirve cómo marcador para desplazarse en el archivo a la hora de la reproducción, aunque se puede omitir, se recomienda incluirlo para agilizar la lectura.
  • 4: VORBIS_COMMENT
    • Este bloque contiene la información relevante a los creadores del contenido multimedia y la descripción del mismo, se usa la especificación de "Vorbis comment"
  • 5: CUESHEET
    • Contiene información que se puede usar en un CD
  • 6: PICTURE
    • Imagen representativa del archivo, similar a la imagen embebida en un archivo MP3.
  • 7-126: Reservado
    • Reservado por ahora, por lo que se podría decir que si un bloque tiene descriptor en este rango, no es valido. Se planea su uso futuro.
  • 127: No válido
    • Bloque no valido, ya que la misma secuencia se usa cómo marcador para sincronización.
Por lo que por ahora solo nos serian relevantes los bloques con descriptores: 0, 4, 6, 7-126 y 127. Especialmente el que tenga descriptor 0, porque es mandatorio; 4, porque seria el equivalente a los TAGS en mp3; y 6, porque es la imagen del álbum (por lo general). Sabiendo los tipos de bloques que vamos a leer, vamos a concéntranos solo en ellos, por lo que primero revisaremos cómo es que está armado el bloque "STREAMINFO".

Bloque "STREAMINFO"

El bloque STREAMINFO, consta de la siguiente secuencia:
  • <16> Tamaño mínimo del bloque.
  • <16> Tamaño máximo del bloque.
  • <24> Tamaño mínimo del "FRAME" en bytes.
  • <24> Tamaño máximo del "FRAME" en bytes.
  • <20> Frecuencia de sampleo.
  • <3> Número de canales.
  • <36> Total de muestras por segundo.
  • <128> MD5 del audio.
Ya que cada una de las secciones tiene un tamaño ya predefinido, podemos asegurar que toda la secuencia siempre tendrá el mismo tamaño y cómo no es necesario que sepamos el contenido de esta, bastará con desplazarnos al siguiente bloque. Antes de continuar, haremos el calculo a mano de la posición en la que empieza y en la que acaba, para eso usaremos XVI32 para ver el contenido de forma hexadecimal, y el archivo de muestra será el que usamos para comparar en el post de "FLAC vs WAV"

Si leemos las cosas en orden, debemos de empezar por buscar la secuencia "fLaC" que serán los primeros 4 bytes.

Ahora que sabemos que el primer bloque debe de ser el bloque STREAMINFO, nos vamos a concentrar en eso, si hacemos memoria, lo primero que debemos de buscar de cada bloque, es su encabezado que serán 32 bits (4 bytes) por lo que inmediatamente después de la secuencia "fLaC" los siguientes bytes corresponden al encabezado de nuestro bloque.

En este caso, el encabezado está representado por la secuencia hexadecimal: 0x00, 0x00, 0x00 y 0x22. ¿Pero cómo la interpretamos? simple, la referencia dice que el primer bit es el indicador del último bloque, solo si es 1, para entender mejor, veamos el número en binario.

Cómo iremos leyendo "de byte en bytes", el mínimo de bits a leer es 8, pero sim problema podemos aislar el primer bit de cada byte y determinar si es o no el último bloque, en este caso, cómo encontramos un 0, indica que hay mas bloques después de este.

Los siguientes 7 bits indicaran el tipo de bloque y para el tipo de bloque debe de ser 0, antes veamos cómo son los números del uno al seis en binario (recuerda que en el blog tenemos DecToAny que sirve para convertir un número decimal a cualquier base hasta 25, puedes descargar la aplicación para Windows, android o usar la versión web).


Pero si no quieres molestarte en revisar, aquí la numeración binaria del cero al seis, que son los valores validos para esta parte de la secuencia.



Cuando leemos la información del bloque encontramos con 0x00 lo que es igual cero decimal o 0 en binario, lo que confirma que el bloque es el bloque STREAMINFO. Los restantes 24 bits indican cual es el tamaño del bloque. Al igual que los hicimos con el primer byte, los siguiente 3, se irán leyendo de uno por uno, pero juntos representan un solo número.

Toda la secuencia anterior representa 34

Si en XVI32 marcamos esos 34 bytes, tendríamos seleccionado todo el bloque.

Y bien, por ahora es todo. para no hacer muy extenso el post, terminaremos por hoy, sirve que da tiempo para leer toda la documentación del los archivos FLAC.
Si bien este post se podría tomar cómo una mala copia de la documentación, no lo es y estoy seguro que a mas de uno le será de ayuda, pero además así es nuestro estilo de trabajo, primero nos ensuciamos las manos descuartizando el archivo con el lector hexadecimal y luego hacemos código que hace lo mismo pero más rápido. Ahora y diferente al caso de ArtView, trataré de hacer versiones para los distintos lenguajes de programación.

Ya es diciembre por lo que publicaré un par de post antes de que acabe el año. Es cierto que la actividad del blog vino en picada, pero para todos aquellos que se quedaron o que regresaron, los post iran saliendo de manera regular.

Los leo luego.

Vamos a programar #99 - Modo obscuro en CSS 3.

 Hola de nuevo a todos, el día hoy vamos a ver como aplicar el modo obscuro a una página web de manera sencilla.



Si te has dado vueltas por el blog, podrás observar que ahora, dependiendo de la configuración del sistema operativo-navegador, la página cambia de color, si el tema es obscuro, la página también lo será y si es claro, pasa lo mismo.

Para poder aplicar un tema de un color para cada caso, existe una forma sencilla de hacerlo y para poder apreciarlo mejor, vamos a ver un ejemplo con los estilos para la calculadora de Vigenere.

Para empezar, veamos cuales son los estilos actuales (dependiendo de cuando leas esto, los estilos pudieron haber cambiado, para que no haya problema puedes ir y revisar el código fuente de la página en vivo).

.h1v
{
    font-size: 16px;
}
.formVigenere{
    font: 95% Arial, Helvetica, sans-serif;
    max-width: 400px;
    margin: 10px auto;
    padding: 16px;
    background: #000;
}
.textareaV
{
    width: 100%;
    height: 150px;
    padding: 5px 8px;
    box-sizing: border-box;
    border: 2px solid #999;
    border-radius: 4px;
    resize: none;
    -webkit-transition-duration: 0.4s; /* Safari */
    transition-duration: 0.4s;
}

.textareaV:focus
{
    width: 100%;
    height: 150px;
    padding: 2px 5px;
    box-sizing: border-box;
    border: 2px solid #50b;
    border-radius: 4px;
    resize: none;
}

.TextV
{
    width: 100%;
    box-sizing: border-box;
    border: 2px solid #999;
    border-radius: 4px;
}

.ButtonV
{
width: 100%;
    background-color: #57a2e2;
    border: none;
  border-radius: 4px;
    color: white;
    padding: 15px 32px;
    text-align: center;
    text-decoration: none;
    display: inline-block;
    -webkit-transition-duration: 0.4s; /* Safari */
    transition-duration: 0.4s;
}

.ButtonV:hover
{
width: 100%;
    background-color: #224360;
    border: none;
    color: white;
    padding: 15px 32px;
    text-align: center;
    text-decoration: none;
    display: inline-block;
}
En CSS3, podemos definir clases que serán estilos para los objetos, cada vez que se le asigne una clase a un objeto, si está definido, este tomara la apariencia que la clase diga, pero además es posible sobrecargar ciertos estilos. En este caso, el primero paso para poder aplicar un tema obscuro, debemos de definir los estilos que serán predeterminados y para esto asumiremos que el estado normal siempre será el tema claro y la mayor parte del tema se basara en este.


Si observamos el caso de nuestro formulario tenemos que el texto es de color negro, el fondo es de color gris claro, los botones son de color azul claro y el área de las cajas de texto es blanco. Para poder idear un esquema de colores para el caso del tema obscuro, uno pensaría que bastaría con invertir los colores, pero no siempre es tan sencillo.

Si observamos la imagen de nueva cuenta, es el resultado de invertir los colores y si no está tan mal, hay que tener en cuanta cual es el color base del tema, en el caso del blog, la mayoría de elementos son de color azul y deberíamos de tratar de preservar la mayor parte de elementos de este color. 

Analizando las partes del formulario, observamos que consta de los siguientes elementos: etiquetas, cajas de texto, botones de opción, botones y el área del formulario. Si por ejemplo observamos que las etiquetas tienen el fondo transparente y el color de las letras es negro, bastará con cambiar el área del formulario que directamente afecta el color de fondo de las etiquetas, que al ser transparentes tomaran el color del área del formulario y por lo cual solo debemos de ocuparnos del color del texto. Para eso bastara con tener en cuenta que para el área del formulario queremos un color muy oscuro o simplemente negro, para las etiquetas vamos a querer un color claro o blanco y para poder elegir uno adecuado, podemos usar las herramientas que nos ofrecen la mayoría de navegadores.

En google chrome si presionamos F12 se abrirá las herramientas para desarrolladores y podremos inspeccionar los elementos que la conforman, al poner el ratón sobre un elemento, se mostraran algunas propiedades y veremos que hay una que dice "Contrast" si aparece un icono de exclamación (cómo en la imagen anterior) quiere decir que la combinación de colores podría resultar difícil de leer.
En cambio si aparece una marca verde, eso indica que la combinación es adecuada para la lectura, pero además nos sirve para crear nuestro esquema de colores.

La ventaja del color azul que seleccioné para el blog es que  resalta, pero no solo en colores oscuros, si no también en los obscuros, por eso podemos cambiar el área del formulario, el color  de las letras en las  etiquetas y con eso bastaría para que la lectura sea agradable em ambos modos: obscuro y claro.

Una vez que hemos determinado que es lo que se va a cambiar, en CSS3 existe una clase o consulta (queries) en la cual se va a aplicar cierto estilo basado en la configuración del sistema operativo, en este caso va a ser en el aspecto, tanto windows cómo Mac os tienen un ajuste en el cual se puede establecer si la mayor parte de los elementos serán claros o obscuro, pero además la mayoría de los navegadores tienen está misma opción.

Para hacer uso debemos usar "@media (prefers-color-scheme: dark)" y cuando el sistema operativo, navegador diga que se debe de usar el tema oscuro, todas las clases que estén aquí se aplicaran automáticamente sobrecargando las actuales (si se usan).

en nuestro caso, veamos que la definición para el área del formulario es:
.formVigenere{
    font: 95% Arial, Helvetica, sans-serif;
    max-width: 400px;
    margin: 10px auto;
    padding: 16px;
    background: #eee;
}

para hacer el cambio, dentro de "@media (prefers-color-scheme: dark)", debemos de crear una subclase que sera igual a la queremos modificar, en este caso, ".formVigenere" y solo incluiremos las propiedades que se van a cambiar en función al color, por lo que tendríamos algo así:
@media (prefers-color-scheme: dark) {
.formVigenere{
  background: #000;
  }
Para cada elemento iremos haciendo lo mismo hasta cubrir los cambio necesarios.

Y bien, por ahora es todo, cómo podrás ver, resulta sencillo hacer el cambio de esquemas y sirve de introducción para poder crear una variedad de temas y no solo claro y obscuro.

Los leo luego.

Vamos a programar #91 - EL triangulo de Pascal (ver. Python)

 Hola de nuevo a todos, el día de hoy vamos a ver cómo hacer el triangulo de pascal usando Python.




Hace unos días mientras leía los comentarios, alguien sugirió que hiciera un programa que calculara el triangulo de pascal y ya que ando un poco corto de ideas, decidí hacerlo. Pero antes de continuar, veamos que es el triangulo de pascal.

En las matemáticas, el triángulo de Pascal es una representación de los coeficientes binomiales ordenados en forma de triángulo. Es llamado así en honor al filósofo y matemático francés Blaise Pascal, quien introdujo esta notación en 1654, en su Traité du triangle arithmétique.1​ Si bien las propiedades y aplicaciones del triángulo fueron conocidas con anterioridad al tratado de Pascal por matemáticos indios, chinos, persas, alemanes e italianos, fue Pascal quien desarrolló muchas de sus aplicaciones y el primero en organizar la información de manera conjunta.2​


El triángulo de Pascal se puede generalizar a dimensiones mayores. La versión de tres dimensiones se llama pirámide de Pascal o tetraedro de Pascal, mientras que las versiones más generales son llamadas simplex de Pascal.

Wikipedia/Triangulo De Pascal

 La composición del triangulo de pascal es realmente simple, en la cima del triangulo, siempre ubicaremos un uno, de hecho en todas las orillas pondremos un uno de modo que en cada nivel haya tantos número cómo el nivel en el que estamos. Para el nivel dos, ya que solo hay extremos en ambos se pondrá un uno.

Para calcular los números del tercer nivel, imaginemos que tenemos tres lugares, los extremos deben de llevar el número uno, por lo que solo nos hace falta determinar el que esta en el lugar dos. Para hacerlo de manera "sencilla" debemos de revisar el nivel anterior y debemos de sumar el numero que esta en la misma posición que el numero que queremos saber (DEL NIVEL ANTERIOR), y sumarlo con el que inmediatamente antes. Para darnos una idea, miremos la siguiente imagen.


Si revisamos bien, nos ubicamos en el nivel tres, pero el numero que queremos saber es el que esta en el segundo lugar del tercer nivel (marcado con una X), entonces tenemos que tomar el numero del nivel anterior que esta en las misma posición que nuestro numero, en la imagen podemos apreciar que el número que esta en la segunda posición del nivel dos es uno y el numero que esta antes en el nivel dos, también es uno, por lo que tendríamos que el segundo numero del tercer nivel es igual a uno mas uno, por lo tanto su valor es dos.


Ahora a manera de ejemplo calculemos el valor de los números en el nivel 4, para eso imaginemos que tenemos algo cómo la imagen que sigue.


Solo tendríamos que determinar X y Y. Para X ya que está en la posición dos del cuarto nivel, tomaríamos los números en la posición dos y uno del tercer nivel y tendríamos cómo resultado 3




Para calcular el numero Y, simplemente hacemos lo mismo. Tomamos el tercer y segundo número del nivel tres y simplemente lo sumamos obteniendo otro tres.


Así podemos calcular cualquier nivel.


Entonces en python podemos crear código cómo el que sigue:


# Prueba del triangulo de pascal en python
def CalculateLevel(Level):
    Result = {}
    if Level == 1:
        Result[0] = 1
    if Level == 2:
        Result[0] = 1
        Result[1] = 1
    if Level > 2:
        ResultP = CalculateLevel(Level - 1)
        for i in range(Level):
            if (i == 0) or (i == Level - 1):
                Result[i] = 1
            else:
                Result[i] = ResultP[i - 1] + ResultP[i]
    return Result
 
def DrawPascalTriangle(Number):
    if Number > 20:
        print("Solo se admiten 20 Niveles")
    else:
        for i in range(Number + 1):
            Result = CalculateLevel(i)
            for j in range(len(Result)):
                print(Result[j], end = '  ')
            print()
    


if __name__ == "__main__":
    import sys
    DrawPascalTriangle(int(sys.argv[1]));


El código consta de dos funciones, la primera de ellas es "CalculateLevel(Level)" y cómo su nombre lo sugiere, calcula los valores para el nivel actual, recibe un parámetro numérico que indica cual es el nivel que queremos calcular. En el algoritmo anterior, vimos que cuando el triangulo consta de un nivel, el valor es uno por lo que creamos una condición y si el nivel solicitado es uno, en un arreglo agregamos un valor de uno. Si la variable "Level" es dos, hacemos lo mismo, solo que agregamos otro valor de uno. Para cuando el valor es mayor que dos, lo primero que hacemos es llamar de forma recursiva a "CalculateLevel" pero usando cómo parámetro el valor de "Level" menos uno así obtenemos el nivel anterior, después hacemos un iterador para recorrer todos las posiciones del nivel y dentro de el primero hacemos una comparación y si el valor del iterador corresponde al primer o al ultimo elemento le asignamos el valor de uno, en caso contrario hacemos los calculo usando el valor del iterador para asignar el valor a la posición actual usando los valores del nivel anterior (cómo explicamos antes). Finalmente devolvemos el valor de la variable "Result" que es donde almacenamos los valores

Después creamos la función "DrawPascalTriangle(Number)" que sirve para "dibujar" el triangulo, ya que la consola suele ser pequeña, si el parámetro "Number" es mayor a 20 escribimos un mensaje diciendo que solo se admiten 20 niveles en caso contrario, hacemos dos iteradores, uno que va a recorrer de uno hasta el nivel dado y a su vez calculará cada nivel y el otro que imprimirá en pantalla todos los valores en cada nivel.

Cómo podrás ver el código es realmente sencillo, para usarlo, simplemente debes de ejecutarlo desde la consola pasando cómo parámetro el valor del nivel hasta el cual quieres calcular
pascaltri.py 20
Y bien por ahora es todo, cómo de costumbre puedes descargar el código de mi dropbox para que lo pruebes antes de que acabe el año voy a publicar las versiones en pascal, en c y probablemente en C# o Javascript.

Los leo luego.

Vamos a programar #87 - Camino a KeyRemap, MessageTextBox .

Hola de nuevo a todos, el día de hoy vamos a empezar un programa que nos sirva para re- configurar las teclas de nuestro teclado.


En el post anterior vimos que es posible re-asignar algunas teclas, todo bien si conocemos cual es el código de la tecla que queremos cambiar (tanto la actual cómo la objetivo) pero actualmente existen teclados que tienen tantas teclas que no están dentro del estandar, que resulta difícil determinar el código buscando en Internet.

En este post veremos cómo es posible determinar el código de cada tecla usando un programa que escribiremos en C# (Si no te interesa, en el siguiente post veremos la versión final de este programa lista para usarse).

Para empezar, primero hay que saber cómo es que se "crean" las pulsaciones. En C# (y probablemente en windows) cuando presionamos una tecla, ocurren los siguientes tres:
  1. KeyDown
  2. KeyPress
  3. KeyUp
KeyDown es el momento en que presionamos la tecla, KeyPress ocurre el tiempo que mantenemos la tecla (usualmente una fracción de segundo pero cuenta aun) y KeyUp que es cuando soltamos la tecla. Windows envía un mensaje diciendo: "Se presiono la tecla con el código XXXX, ahora se mantiene la tecla con el código XXXX y se acaba de soltar la tecla con el código XXXX", cómo son mensajes del sistema operativo, es posible leerlos desde C#, para eso disponemos de dos formas. La primera de ellas es usando lo eventos "_KeyDown", "_KeyPress" y "_KeyUp", muchos de los controles que se pueden añadir a c# disponen de ellos, pero es mas común usarlos en controles "TextBox". Cuando se crea el procedimiento que maneja el evento, se usan dos parámetros, el primero del tipo "object" llamado "sender" y el segundo del tipo "KeyEventArgs" llamado "e"; en este, hay varios procedimientos que nos podrían ser de utilidad, uno de ellos llamado "KeyCode".

Supongamos que tenemos un control "ComboBox" llamado "MsCboKeyOut" al crear el manejador para el evento tendríamos algo cómo lo que sigue:
		private void MsCboKeyOut_KeyDown(object sender, KeyEventArgs e)
		{
			MessageBox.Show(e.KeyCode.ToString());
			e.SuppressKeyPress = true;
		}


Si accedemos al procedimiento "KeyCode" y presionamos la tecla de silencio (mute), esta nos devolverá un valor de 173. Si hacemos memoria, en el post anterior vimos que el código de esa tecla era E020 hexadecimal o 57376 decimal que dista mucho de lo que nos entrego. Incluso haciendo pruebas con varias teclas, el valor maximo que obtuve fue 255 decimal (FF hexadecimal) por lo que si queremos usar este procedimiento para saber el código de las teclas "no normales" simplemente no nos sera posible.

La manera correcta de  de saber el valor real de la tecla, es leyendo directamente el mensaje  lo lleva. En C#, podemos sobrecargar el procedimiento "WndProc" que básicamente sirve para leer los mensajes. Pero cómo queremos ser capaces de utilizar el valor cuando el formulario tiene el foco (que es lo mas común), crearemos un control sencillo con la sobrecarga para que asi podamos utilizarlo.


using System;
using System.Windows.Forms;

namespace KeyRemap
{
	class MessageTextBox : TextBox
	{
		public const int WM_KEYDOWN = 0x0100;
		private uint TheValue = 0;

		public string GetHexValue
		{
			get
			{
				return Convert.ToString(TheValue, toBase: 16);
			}
		}
		public uint ShowKeyCode(uint TheNumber)
		{
			uint KeyValue = TheNumber;
			uint KeyCode = ((KeyValue >> 16) % 256);
			uint IsExtendedKey = ((KeyValue >> 24) % 2);
			if (IsExtendedKey == 1)
			{
				KeyCode = FrmMain.EXTENDED_KEY_VALUE | KeyCode;
				return KeyCode;
			}
			else
				return KeyCode;
		}

		[System.Security.Permissions.PermissionSet(System.Security.Permissions.SecurityAction.Demand, Name = "FullTrust")]
		protected override void WndProc(ref Message m)
		{
			switch (m.Msg)
			{
				case WM_KEYDOWN:
					base.Text = "";
					base.Text = Convert.ToString(ShowKeyCode((uint)m.LParam));
					TheValue = (uint)m.LParam;
					break;
			}
			base.WndProc(ref m);
		}
	}
}



Con este control será mas sencillo interceptar el mensaje que contiene el código de la tecla. Para usarlo, simplemente agrega una nueva clase a tu proyecto y  el evento "_KeyDown" del parámetro "e" , la propiedad "SuppressKeyPress" se debe de establecer el valor de "true" para que el texto que se escriba en el cuadro de texto, corresponda solamente al código de la tecla, si no se suprime la pulsación, si por ejemplo presionamos la tecla "u", en la caja de texto diría "uxx" (donde xx es el código de la tecla u).

Por ahora solo veremos esto y cómo aun falta implementar mucho código, la explicacion detallada vendrá en el siguiente post (junto con la versión ultra beta de "KeyRemap M"), por ahora si sabes programar en C# puede probar el control (y si algo anda mal me pueden avisar).

Los leo luego.

Vamos a programar #86 - Reasignando teclas del teclado.

Hola de nuevo a todos, el día de hoy vamos a ver cómo reasignar algunas botones de nuestro teclado para que hagan otra cosa.

Hace unos días en la eterna lucha del hombre contra la máquina, mi teclado simplemente decidió que 15 años de uso eran suficientes y simplemente ya no quiso funcionar. Mi teclado compaq 5185 fue el primero que tuve y lo use de forma bastante extenuante.
Algo paso que simplemente ya no quiso reconocer las pulsaciones. Cómo siempre que "reparo" algo hay una enorme tendencia a que me "sobren" piezas, simplemente decidí guardarlo. Para su reemplazo, saque un teclado que un amigo me regaló hace mucho (cómo el de la primer imagen), es más compacto y mas viejo que el mio pero totalmente funcional salvo por unos cuantos botones.

Mi primer teclado tenía la ventaja que tenia varios botones adicionales; los mas importantes (para mi) eran: volumen arriba, volumen abajo, silencio y calculadora. El reemplazo también tiene unos botones extra (seis para ser exacto), pero al conectarlo y presionarlos, ninguna hizo nada. Busqué los controladores pero no encontré ninguno para mi sistema operativo. Las teclas "normales" funcionan cómo deben, pero las "especiales" no.

Para solucionar mi problema busque en internet y encontré que todas las pulsaciones del teclado son gestionadas por el sistema operativo, es decir, si presionamos una tecla, está genera un código pero nuestro amigo Windows es el responsable de que pasa después y no solo eso, posee la capacidad de reasignar las pulsaciones.

Tomando en cuenta lo anterior, vamos a buscar una forma de re-asignar una tecla, pero hay una advertencia, vamos a modificar el registro de Windows (el registro prácticamente es un lugar donde se guardan las configuraciones de una computadora con ese sistema operativo), si sigues los pasos que voy a mostrar no habrá ningún problema, pero si mueves más "cosillas" puedes hacer que el PC no inicie correctamente.

Antes que nada veamos unos cuantos códigos para algunas teclas.

   
US   key assignment   
   
Base   Make   
   
Insert   
   
E0   52   
   
Delete   
   
E0   53   
   
Left   Arrow   
   
E0   4B   
   
Home   
   
E0   47   
   
End   
   
E0   4F   
   
Up   Arrow   
   
E0   48   
   
Dn   Arrow   
   
E0   50   
   
Page   Up   
   
E0   49   
   
Page   Down   
   
E0   51   
   
Right   Arrow   
   
E0   4D   

Para nuestra demostración vamos a usar la tecla "Page Up" y la vamos a reasignar para silenciar el volumen del sistema. La siguiente tabla muestra  algunos de los códigos de teclas multimedia.

CODEALIAS
E0 6AVK_BROWSER_BACK
E0 69VK_BROWSER_FORWARD
E0 67VK_BROWSER_REFRESH
E0 68VK_BROWSER_STOP
E0 65VK_BROWSER_SEARCH
E0 66VK_BROWSER_FAVORITES
E0 32VK_BROWSER_HOME
E0 20VK_VOLUME_MUTE
E0 2EVK_VOLUME_DOWN
E0 30VK_VOLUME_UP
E0 19VK_MEDIA_NEXT_TRACK
E0 10VK_MEDIA_PREV_TRACK
E0 24VK_MEDIA_STOP
E0 22VK_MEDIA_PLAY_PAUSE
E0 6CVK_LAUNCH_MAIL
E0 6DVK_LAUNCH_MEDIA_SELECT
E0 6BVK_LAUNCH_APP1
E0 21VK_LAUNCH_APP2

El código de la tecla que queremos usar es "E0 20" y el código de la tecla "Page UP" es "E0 49" pero ¿y después? Cómo ya mencione antes, Windows es el encargado de gestionar las pulsaciones y guarda esta configuración en el registro. Para acceder a ella, necesitamos encontrar la siguiente clave:

[HKEY_LOCAL_MACHINE\SYSTEM\CurrentControlSet\Control\Keyboard Layout]

Ahí crearemos un nuevo valor binario.


La estructura que debe de tener el valor es de la siguiente forma:

  • 00,00,00,00 > encabezado
  • 00,00,00,00 > flags
  • 02,00,00,00 > número de entradas más uno
  • xx,xx,xx,xx > el código de las teclas a reasignar empezando por el código que queremos que se aplique seguido por el código de la tecla
  • 00,00,00,00 > terminación.
Entonces si sustituimos los valores de nuestras teclas, tenemos algo cómo de la imagen:


Hay que notar que cada código tiene los valores "invertidos", es decir, la tecla de silencio tiene el código "E0 20" pero al momento de escribir el valor en el registro, lo ponemos cómo "20 E0" y lo mismo va para todas las teclas, la tecla "Page Up" tiene el codigo "E0 49" pero lo escribimos cómo "49 E0". Al reiniciar la computadora y presionamos la tecla "Page Up", el sistema se debería de silenciar (en caso contrario algo salio mal). Si quieres hacer el proceso de forma automática, puedes copiar el siguiente texto y guardarlo cómo "key.reg" usando el bloc de notas.

Windows Registry Editor Version 5.00

[HKEY_LOCAL_MACHINE\SYSTEM\CurrentControlSet\Control\Keyboard Layout]
"Scancode Map"=hex:00,00,00,00,00,00,00,00,02,00,00,00,20,E0,49,E0,00,00,00,00

Cada vez que hagas un cambio se debe de reiniciar la computadora.
Finalmente y a manera de ejemplo, supongamos que queremos cambiar las teclas que ya cambiamos, pero ademas queremos que la tecla numérica 7 suba el volumen, tendríamos algo cómo lo que sigue:

  • 00,00,00,00 > encabezado
  • 00,00,00,00 > flags
  • 03,00,00,00 > número de entradas más uno
  • 20,e0,49,e0 > el código del primer juego de teclas
  • 30,e0,47,00 > el código del segundo juego de teclas
  • 00,00,00,00 > terminación.
Cómo podrás ver, hay una gama amplia de posibilidades, solo ten en cuenta que los cambios son solo validos para teclas simples, si intentas crear combinaciones de teclas (ctrl + shift +supr) estas no funcionaran. En el siguiente post continuaremos con mas de esto y aprenderemos a detectar cuales son los códigos de cualquiera de las teclas de nuestro teclado.

Los leo luego.

Back to basics #10 - Reguladores de voltaje

Hola de nuevo a todos, el día de hoy vamos a ver el uso del regulador de voltaje.



A veces cuando trabajamos con algunos componentes, es necesario tener una fuelte de voltaje que nos suministre un voltaje constante. Por lo regular, la mayoria de las aplicaciones del blog necesitan un voltaje de 5v. Si trabajamos con una fuente de voltaje de laboratorio, podemos confiar que tendremos siempre disponible "la energia necearia", pero que pasa si queremos usar un eliminador y solo tenemos uno de 12v? Simple, no podremos usarlo o no de forma directa (al menos que nos guste ver el mundo arder).

Este tipo de eliminador es bastante barato.

En la medida de lo posible deberíamos de usar componentes que soporten un rango amplio de operación, pero el caso de que queramos  combinar componentes que usen 5v y 12v es donde surgirá un problema.

El circuito LM7805 (o simplemente 7805) sirve para regular un voltaje, éste modelo en especifico sirve para regularizar el voltaje a un nivel de 5v. Existen mas variantes del integrado; el 7809 y el 7812 y el voltaje de regulación de cada uno es 9v y 12v respectivamente. Su uso es realmente sencillo y si miramos la siguiente imagen, veremos el diagrama de conexión.

Físicamente el regulador puede tener alguno de los siguientes aspectos:
Y nosotros por ahora usaremos el T0-220.  La pata uno será por donde conectaremos el voltaje a regular; 12V en este caso. La para 2 es tierra y la pata 3 es por donde saldrá el voltaje ya regulado.Si observamos bien el diagrama, solamente necesitaremos un capacitor a la entrada y uno más a la salida y obtendremos un voltaje. Los valores de estos dependerán en gran medida de la corriente que el regulador deberá suministrar. En la hoja de datos para el integrado LM7805 se indica que dependiendo de la lejanía de la entrada con respecto a la fuente de alimentación, el capacitor C1 puede ser omitido, mientras que el capacitor C2 ayuda a mantener un valor estable. Cómo recomendación puedes probar con distintos valores o solo usar los valores recomendados en la hoja de datos que son: C1 = 0.22µf y C2= 0.1µf. Personalmente he probado con valores de 2µf en la entrada y salida para cuando uso una carga de 400mA.

Una de las consideraciones que debemos de tomar en cuenta, es que el regulador va a disipar todo el exceso de voltaje en calor por lo que si queremos hacer uso de este y le vamos a exigir corrientes superiores a 500mA, debemos de utilizar un disipador de calor.(aunque se supone que tiene protección contra sobre-corriente y sobre-calentamiento probablemente los vecinos sufran si no hay una buena disipación)

Las cosas se pueden poner "calientes" si la disipacion adecuda

Para recapitular. El integrado lm7805 (y todos sus amigos) pueden ser una alternativa realmente rápida y sencilla para obtener un voltaje regulado, pero está por demás decir que debido a que todo el "voltaje extra" se convierte en calor, no es la manera más eficiente para hacerlo pero si la corriente que usamos no es tan alta sin duda es una buena alternativa , para corrientes mayores existen otro tipo de reguladores (que eventualmente veremos) pero si no queremos complicarnos, está sin duda es una buena alternativa.

Y bien, por ahora es todo. La cuarentena lejos de permitirme crear cosas para el blog, me alejo de él, pero aprovechando que en este mes cumplimos un año más, trataré de subir algunos post útiles