Los archivos Jpeg además de la propia imagen contienen información extra añadida por algunos programas (comentarios, tipo de cámara, thumbnails, etc) que engordan el archivo pero que no son necesarios para ver la imagen.
En este truco vamos a eliminar toda esa información extra y dejar el archivo lo mas pequeño posible sin modificar en nada la calidad de la imagen.
procedure Limpiar(Origen, Destino: TStream); var Buffer: array[0..$FFFF] of Char; Header: array[1..4] of byte; Size: Integer; begin Origen.Seek(0,soFromBeginning); Origen.ReadBuffer(Header,2); if (Header[1] <> $FF) or (Header[2] <> $D8) then raise Exception.Create('Identificador incorrecto'); Destino.WriteBuffer(Header,2); repeat Origen.ReadBuffer(Header,4); Size:= ((Header[3] shl 8) + Header[4]) - 2; case Header[2] of // $FE: Origen.Seek(Size,soFromCurrent); $FE, $E0..$EF: Origen.Seek(Size,soFromCurrent); $01,$D0..$D9: begin Destino.WriteBuffer(Header,2); Origen.Seek(-2,soFromCurrent) end else begin Origen.ReadBuffer(Buffer,Size); Destino.WriteBuffer(Header,4); Destino.WriteBuffer(Buffer,Size); end; end; until Header[2] = $DA; Destino.CopyFrom(Origen,Origen.Size - Origen.Position); end; // Ejemplo de uso var Origen, Destino: TFileStream; begin if OpenDialog1.Execute then begin Origen:= TFileStream.Create(OpenDialog1.FileName, fmOpenRead); try Destino:= TFileStream.Create(ChangeFileExt(OpenDialog1.FileName,'_b.jpg'),fmCreate); try Limpiar(Origen,Destino); finally Destino.Free; end; finally Origen.Free; end; end; end;
Esta limpieza además de ajustar el tamaño del archivo, ayuda a mantener nuestra intimidad, ya que borra también las imágenes en miniatura, conocidas como thumbnails, que algunas aplicaciones guardan dentro del archivo jpeg. Se han dado casos de programas, que al recortar la imagen, siguen manteniendo la imagen en miniatura de la imagen completa, revelando así partes de la imagen que queríamos ocultar. Una buena limpieza y problema resuelto ...
Ahora poniéndonos del otro lado, del lado de los curiosos, podemos pensar una manera de extraer las imágenes en miniatura de los archivos jpeg y así comprobar si alguien a cometido una indiscreción. Podríamos aplicar un método mas ortodoxo, siguiendo el formato Exif por el que se rigen este tipo de miniaturas, pero lo que vamos a hacer es extraer el segmento que contiene la información Exif, una vez lo tenemos buscamos la marca $FFD8 que indica el inicio de una imagen jpeg. Esto no es la forma mas correcta de hacerlo, ya que nos saltamos toda la estructura de datos Exif, pero el caso es que funciona y para jugar un rato nos sirve.
function ReadThumb(Source, Dest: TStream): Boolean; var Buffer: array[0..$FFFF] of Char; Header: array[1..4] of byte; Size: Integer; i: Integer; begin Result:= FALSE; Source.Seek(0,soFromBeginning); Source.ReadBuffer(Header,2); if (Header[1] <> $FF) or (Header[2] <> $D8) then raise Exception.Create('Identificador incorrecto'); repeat Source.ReadBuffer(Header,4); Size:= ((Header[3] shl 8) + Header[4]) - 2; case Header[2] of $E1: begin Source.ReadBuffer(Buffer,Size); i:= 0; while i < Size - 1 do begin if CompareMem(@Buffer[i],PChar(#$FF#$D8),2) then begin Dest.WriteBuffer((@Buffer[i])^, Size - i); Result:= TRUE; break; end; inc(i); end; break; end; $01,$D0..$D9: Source.Seek(-2,soFromCurrent); else Source.Seek(Size,soFromCurrent); end; until (Header[2] = $DA); end; procedure ExtractThumb(Filename: string); var Source: TFileStream; Thumb: TMemoryStream; begin Source:= TFileStream.Create(Filename, fmOpenRead); try Thumb:= TMemoryStream.Create; try if ReadThumb(Source,Thumb) then Thumb.SaveToFile(ChangeFileExt(Filename,'_thumb.jpg')); finally Thumb.Free; end; finally Source.Free; end; end; // Por ejemplo ExtractThumb('D:\1.jpg');
En el ejemplo anterior si "d:\1.jpg" contiene un imagen en miniatura, se guardara en el archivo "d:\1_thumb.jpg", y si no contienen ninguna no se crea ningún fichero. Ahora solo tenemos que buscar un directorio con un buen numero de jpegs y hacerlo con todas las imágenes para ver si descubrimos algo ...
Comentarios
¿y otros números mágicos para
¿y otros números mágicos para ficheros .png, .gif, jpeg2000, .bmp, etc ?
Thx
Los números FFD8 no son tan
Los números FFD8 no son tan "mágicos" simplemente es la marca con lo que empiezan todos los ficheros jpeg según las especificaciones de ese formato. Para otro tipo de ficheros habría que encontrar las especificaciones de ese formato, ver si existe algún tipo de marca reconocible (por ejemplo los bmp empiezan con los caracteres "BM"), y luego encontrar la forma de leer los datos sin conocer de antemano el tamaño del fichero.
Para empezar puedes buscar las especificaciones en esta pagina:
http://www.wotsit.org/