Extraer imagenes de un fichero MPO

Recientemente han llamado mi atención sobre el formato de imagen MPO (Multi-Picture Format), un formato que se usa dentro del campo de la fotografía en 3D, y concretamente es el utilizado por la cámara FinePix REAL 3D. Básicamente un fichero MPO esta compuesto por una imagen principal, y una serie de imágenes secundarias, guardadas en formato JPEG. La imagen principal está colocada justo al comienzo del fichero de tal forma que podría ser vista por cualquier visor de imágenes jpeg, mientras que para ver el resto de imágenes necesitamos de algún software especifico que soporte este formato.

Como decía, mi interés por este formato viene dado por una pregunta de otro programador que quería saber como extraer de un fichero MPO cada una las diferentes imágenes jpeg que lo componen. Entonces recordé el código para recuperar imágenes jpeg que coloque por aquí, si bien ese programa estaba pensado para recuperar fotos borradas, también sirve para extraer una imagen jpeg de cualquier archivo en el que este incrustada (pdf, thumb.db, etc ...) y por lo tanto tampoco tiene ningún problema en extraer las imágenes jpeg de un fichero MPO.

El problema que surgió entonces fue la velocidad, el proceso era muy lento, sobre todo con imágenes grandes. Pero la solución era sencilla, cargar el fichero MPO en memoria antes de comenzar a explorarlo, esto incrementa de forma radical la velocidad de todo proceso. En el código original no se cargaba el fichero de origen en memoria porque estaba pensado para examinar discos completos, y no tiene mucho sentido cargar un fichero de varios gigas en memoria, pero cuando tratamos con ficheros de pocos megas, como es el caso de los MPO no hay problema.

El resultado de todo lo anterior es este pequeño programa:

program mpo2jpg;
 
uses
  Windows, SysUtils, Classes;
 
procedure CopyJpegFrom(Src, Dst: TStream);
var
  Buffer: PByteArray;
  Size: Integer;
begin
  GetMem(Buffer,$10000);
  try
    while TRUE do
    begin
      FillChar(Buffer^,4,#0);
      Src.Read(Buffer^,4);
      Size:= ((Buffer[2] shl 8) + Buffer[3]) - 2;
      case Buffer[1] of
        $01,$D0..$D8: begin
                        Dst.WriteBuffer(Buffer^,2);
                        Src.Seek(-2,soFromCurrent);
                      end;
        $D9: begin
               Dst.WriteBuffer(Buffer^,2);
               Exit;
             end;
        $DA: begin
               Dst.WriteBuffer(Buffer^,2);
               Src.Seek(-2,soFromCurrent);
               while TRUE do
               begin
                 Src.ReadBuffer(Buffer^,1);
                 if Buffer[0] = $FF then
                 begin
                   Src.ReadBuffer(Buffer^,1);
                  if (Buffer[0] <> 0) and not (Buffer[0] in [$D0..$D7]) then
                   begin
                     Src.Seek(-2,soFromCurrent);
                     break;
                   end else
                   begin
                     Buffer[1]:= Buffer[0];
                     Buffer[0]:= $FF;
                     Dst.WriteBuffer(Buffer^,2);
                   end;
                 end else
                   Dst.WriteBuffer(Buffer^,1);
               end;
             end
        else
        begin
          Dst.WriteBuffer(Buffer^,4);
          Src.ReadBuffer(Buffer^,Size);
          Dst.WriteBuffer(Buffer^,Size);
        end;
      end;
    end;
  finally
    FreeMem(Buffer);
  end;
end;
 
procedure SaveJpegToFile(Src: TStream; Filename: String);
var
  Stream: TMemoryStream;
begin
  Stream:= TMemoryStream.Create;
  try
    CopyJpegFrom(Src, Stream);
    Stream.SaveToFile(Filename);
  finally
    Stream.Free;
  end;
end;
 
procedure ScanFile(Stream: TStream; FormatStr: String; Index: Integer; Full: Boolean); overload;
var
  B: Byte;
  Leidos: Integer;
  Posicion: int64;
  Str: String;
begin
  // Leemos byte a byte hasta encontrar la marca de inicio
  Leidos:= Stream.Read(B,1);
  while Leidos = 1 do
    if B = $FF then
    begin
      Leidos:= Stream.Read(B,1);
      if Leidos = 1 then
        if B = $D8 then
        begin
          Leidos:= Stream.Read(B,1);
          if Leidos = 1 then
            if B = $FF then
            begin
              // Encontramos la marca de inicio
              Posicion:= Stream.Position;
              Stream.Seek(-3,soFromCurrent);
              Str:= Format(FormatStr,[index]);
              try
                // Intentamos extraer la imagen
                SaveJpegToFile(Stream,Str);
                inc(index);
              except
                // Si se produce un error volvemos atras y continuamos buscando
                On E: Exception do
                begin
                   Stream.Position:= Posicion;
                end;
              end;
              // Si la busqueda es "completa", extraemos tambien los thumbnails
              if Full then              
                Stream.Position:= Posicion;
            end;
        end;
    end else
      Leidos:= Stream.Read(B,1);
end;
 
var
  Src: TMemoryStream;
 
begin
  if ParamCount = 0 then
    Exit;
  try
    Src:= TMemoryStream.Create;
    try
      // Cargamos el archivo en memoria
      Src.LoadFromFile(ParamStr(1));
      // Exploramos el archivo cargado en la memoria
      ScanFile(Src,ChangeFileExt(ParamStr(1),'_%d.jpg'),1,FALSE);
    finally
      Src.Free;
    end;
  except
    On E: Exception do
    begin
       // Si tenemos algun error lo mostramos
       MessageBox(0,PChar(E.Message),'Error',MB_ICONERROR or MB_SETFOREGROUND
         or MB_TASKMODAL or MB_OK);
    end;
  end;
end.

Su uso es sencillo, solo hay que pesarle como parámetro el fichero MPO (o simplemente arrastrar el fichero y soltarlo sobre el .exe) y automáticamente el programa extraerá la imágenes en el mismo directorio del fichero original (hay que comprobar primero que ese directorio no sea de "solo lectura").

Por ejemplo:

mpo2jpg ejemplo.mpo

El programa completo, con sus fuentes, se puede bajar de aquí. También se puede utilizar para extraer imágenes de ficheros pdf, thumb.db, etc ... siempre que tengan un tamaño razonable.

Para saber más sobre el formato MPO:
http://www.cipa.jp/english/hyoujunka/kikaku/pdf/DC-007_E.pdf

Comentarios

Muchas gracias por compartir el código fuente. Además, es un programa muy útil.

Hola, muchas gracias por este programa. La cámara fuji3d está configurada de tal manera que no puedes hacer las fotos simultáneas idénticas. De esta manera no podía sacar las fotos para montarlas en un estereoscopio, o las tenía que manipular.
Así que me viene fenomenal. Mil gracias.
María.

Sabes como puedo recuperar la otra imagen, solo aparece la principal y quisiera tener las 2 (izquierdo y derecho)
gracias

Pues deberían de aparecer las dos, si no aparecen es que en el fichero solo hay una imagen y no dos.

Saludos

En la página de fuji se puede descargar el programa myfinepix studio en donde se pueden crear archivos mpo y separar los existentes, el problema es que hay que hacerlo de uno por uno y es tedioso y muy lento. saludos!!!