Преобразование изображений в формате JPEG в Bmp - некоторые изображения получаются голубыми - PullRequest
10 голосов
/ 18 января 2012

Есть несколько изображений Jpg, которые Delphi, похоже, не нравятся.Похоже, с файлами, которые я загружаю.И процедура проста: а) загрузить изображение Jpg в TJpegImage, б) назначить объект Jpg объекту TBitmap и в) сохранить и / или отобразить изображение Bmp.По какой-то причине эти изображения продолжают получаться с голубоватым оттенком.

Эти изображения прекрасно отображаются в любом месте и везде, где я их загружаю (программа просмотра изображений Windows, краски, фотошоп и т. Д.).

И то, что я делаю, очень просто ...

procedure Load;
var
  J: TJpegImage;
  B: TBitmap;
begin
  J:= TJpegImage.Create;
  B:= TBitmap.Create;
  J.LoadFromFile('C:\SomeFile.jpg');
  B.Assign(J);
  //Either save or display `B` and it appears blueish at this point
....

Я хочу избежать как можно большего количества посторонних вещей.Эта проблема существовала в Delphi версий 7, 2010 и XE2.По крайней мере, элемент управления TImage в XE2 отображает его правильно (в отличие от двух старших), но это не имеет значения, если TBitmap все еще не работает.Что не так с этим файлом?И / или что не так с рендерингом Delphi?

Дополнительная информация

Я недавно кое-что узнал об этих изображениях.Когда они пришли от поставщиков (изображения продуктов), они были в формате CMYK.В то время Delphi 7 не поддерживал эти файлы должным образом (с нарушениями доступа и плохими изображениями), поэтому все изображения были отфильтрованы через конвертер в цветной формат RGB.Многие оригинальные изображения были также в формате TIFF и были преобразованы в JPG.Таким образом, похоже, что программное обеспечение FastStone Image Resizer не должно правильно сохранять эти файлы, когда они проходят.Синее изображение встречается не на всех из них, только несколько случайных партий за раз.Программное обеспечение обрабатывает тысячи продуктов, поэтому существует тысячи возможных изображений.

Ответы [ 4 ]

11 голосов
/ 19 января 2012

Причина, по которой ваш файл синий, заключается в том, что кодировка BGR вместо RGB.
Если вы измените исходный файл jpeg.pas и используете замену пикселей (удалите {.$IFDEF JPEGSO} в TJPEGImage.GetBitmap), вы увидитеВаш образец файла правильно коричневый.

Итак, я думаю, суть в том, что исходный jpeg-источник не определяет правильную (обратную) кодировку;вероятно в jc.d.out_color_space ...

Обновление:
Исходный файл C (и jpeg.pas) должен объявить (и использовать) Цветовые пространства с новыми расширениями JCS_EXT _...:

enum J_COLOR_SPACE {
  JCS_UNKNOWN, JCS_GRAYSCALE, JCS_RGB, JCS_YCbCr,
  JCS_CMYK, JCS_YCCK, JCS_EXT_RGB, JCS_EXT_RGBX,
  JCS_EXT_BGR, JCS_EXT_BGRX, JCS_EXT_XBGR, JCS_EXT_XRGB
}

Обновление 2:
jpeg.pas можно найти (XE) в C:...\RAD Studio\8.0\source\vcl с файлами C в подпапке jpg.

Если вы готовы поспорить, что все файлы Adobe с цветовым пространством RGB должны поменяться местами, вы можете легко взломать источник jpeg.pas, чтобы обнаружить ваши специальные файлы и условно выполнить обмен, упомянутый выше вTJPEGImage.GetBitmap

{.$IFDEF JPEGSO}
          if (jc.c.in_color_space=JCS_RGB)and
            (smallint(jc.c.jpeg_color_space)=Ord(JCS_UNKNOWN))and   //comes 1072693248 = $3FF00000 = 111111111100000000000000000000
            jc.d.saw_Adobe_marker  and
            (PixelFormat = jf24bit) then
          begin
8 голосов
/ 18 января 2012

WIC (доступно для XP и выше) может обрабатывать это изображение. Этот компонент хорошо обернут в Delphi 2010 и выше. Для более ранних версий Delphi достаточно просто вызвать WIC с помощью интерфейсов COM.

Вот мое подтверждение концепции кода:

var
  Image: TWICImage;
  Bitmap: TBitmap;
begin
  Image := TWICImage.Create;
  Image.LoadFromFile('C:\desktop\ABrownImage.jpg');
  Bitmap := TBitmap.Create;
  Bitmap.Assign(Image);
  Bitmap.SaveToFile('C:\desktop\ABrownImage.bmp');
end;

Примечание 1 : WIC поставляется с Vista, но должен быть перераспределен для XP. Один из очевидных вариантов - использовать WIC, если он доступен, но в противном случае прибегнуть к декодеру Delphi JPEG.

Примечание 2 : Я не могу найти распространяемый пакет для WIC. Я подозреваю, что это может потребовать загрузки конечного пользователя для XP. Это сказало, что я не буду вообще удивлен, если бы подавляющее большинство машин XP установило это к настоящему времени.

5 голосов
/ 19 января 2012

Я разобрался в проблеме. Скорее всего, это ошибка в Delphi.

Предоставленное изображение является своеобразным форматом для файла JPEG, который называется Adobe JPEG. Вероятно, самая специфическая вещь в Adobe JPEG - это то, что он позволяет хранить изображение в формате RGB, хотя он также допускает другие форматы. Большинство файлов JPEG имеют формат JFIF или EXIF, в которых не используется RGB.

При копировании данных RGB независимо от того, что делает Delphi, они переворачивают красные и синие данные, когда загружают их на холст. Он загружается как BGR вместо RGB. Это может быть связано с тем, что Windows (24-битные и 32-битные) DIB (BMP) хранятся в формате BGR.

Я предполагаю, что ошибка появится в Delphi для любого RGB JPEG. Поскольку большинство JPEG-файлов не используют RGB, частота появления ошибок низкая. Простое решение, если у вас есть источник для устройства JPEG, - это изменить порядок при загрузке RGB JPEG.

Если у вас нет источника, продолжайте.

Adobe JPEG указывает порядок цветов в таком формате (в шестнадцатеричном формате), как это 43 11 00 47 11 00 42 11 00, который выглядит в шестнадцатеричном редакторе R..G..B. Если вы измените значения R и B здесь через редактор Hex, в Windows это будет неправильно, а в Delphi - прямо.

Чтобы распознать Adobe JPEG, первые четыре байта являются (в шестнадцатеричном формате) FF D8 FF ED или FF D8 FF EE, причем ED и EE являются дифференцирующими байтами. Все файлы JPEG начинаются с FF D8 FF.

После этих байтов два байта, которые представляют длину маркера типа, затем (в ASCII) Adobe, за которыми следуют еще шесть байтов (представляющих версию и т. Д.) И, наконец, (18-й байт) байт, который определяет формат. 0 означает RGB. Итак, проверьте эти значащие байты и затем действуйте соответствующим образом.

Вам придется изменить порядок RGB в заголовке файла (чтобы обмануть Delphi) или скопировать его в TBitmap и использовать ScanLine, чтобы обратить RGB к правильному порядку.

Сведения о формате взяты из чтения источника libJPEG в C.

1 голос
/ 20 января 2012

В ответ на ваш другой вопрос приведен код для загрузки файла JPG в растровое изображение и условного применения исправления к полученному растровому изображению.Обратите внимание, что это работает для вашего изображения Brown.JPG, но я понятия не имею, что находится в этих первых 18 байтах, поэтому я понятия не имею, будет ли это работать долго или нет.Я лично предпочел бы использовать готовую, известную в работе, широко используемую библиотеку.В качестве альтернативы я бы использовал идею Дэвида использовать WIC, если он доступен, и вернуться к этому стилю хакерского кода, если он недоступен.

Вот код полный , чтобы вы могли увидеть все использованные модули,Форма ожидает только одну TImage с именем Image1 в форме, поэтому вы можете сначала создать свою форму, поместить туда TImage, а затем переключиться в представление исходного кода и скопировать и вставить мой код поверх кода, созданного в Delphi..

Как работает код:

Код открывает файл с изображением JPG и загружает его в TJpgImage.Затем он сравнивает первые 18 байтов файла с известным маркером.Если есть совпадение, оно применяет преобразование к каждому пикселю производимого растрового изображения.Поскольку записать фактические константы маркера сложно, есть подпрограмма (CopyConstToClipboard), которая берет байты из файла, преобразует их в константу в стиле Delphi и копирует ее в буфер обмена.Когда вы найдете новый файл, который не работает, вы должны использовать эту процедуру для подготовки новой константы.

Фактический код:

unit Unit9;

interface

uses
  Windows, Messages, SysUtils, Variants, Classes, Graphics, Controls, Forms,
  Dialogs, ExtCtrls, Jpeg, Clipbrd;

type
  TForm9 = class(TForm)
    Image1: TImage;
    procedure FormCreate(Sender: TObject);
  private
    { Private declarations }
  public
    { Public declarations }
  end;

var
  Form9: TForm9;

implementation

{$R *.dfm}

type
  TRGB_Pixel = packed record
    B1: Byte;
    B2: Byte;
    B3: Byte;
  end;
  TScanLine = array[0..(System.MaxInt div SizeOf(TRGB_Pixel))-1] of TRGB_Pixel;
  PScanLine = ^TScanLine;

procedure CopyConstToClipboard(const FB:array of byte);
var s: string;
    i: Integer;
begin
  s := 'Name: array[0..' + IntToStr(High(FB)) + '] of Byte = ($' + IntToHex(FB[0], 2);
  for i:=1 to High(FB) do
    s := s + ', $' + IntToHex(FB[i],2);
  s := s + ');';
  Clipboard.AsText := s;
end;

function LoadJpegIntoBitmap(const FileName:string): TBitmap;
var F: TFileStream;
    Jpg: TJPEGImage;
    FirstBytes:array[0..17] of Byte;
    y,x: Integer;
    ScanLine: PScanLine;
const Marker_1: array[0..17] of Byte = ($FF, $D8, $FF, $EE, $00, $0E, $41, $64, $6F, $62, $65, $00, $64, $00, $00, $00, $00, $00);

  procedure SwapBytes(var A, B: Byte);
  var T: Byte;
  begin
    T := A;
    A := B;
    B := T;
  end;

begin
  F := TFileStream.Create(FileName, fmOpenRead or fmShareDenyWrite);
  try
    Jpg := TJPEGImage.Create;
    try
      Jpg.LoadFromStream(F);
      F.Position := 0;
      F.Read(FirstBytes, SizeOf(FirstBytes));

      // CopyConstToClipboard(FirstBytes); // Uncomment this to copy those first bytes to cliboard

      Result := TBitmap.Create;
      Result.Assign(Jpg);

      if (Result.PixelFormat = pf24bit) and CompareMem(@Marker_1, @FirstBytes, SizeOf(FirstBytes)) then
      begin
        for y:=0 to Result.Height-1 do
        begin
          ScanLine := Result.ScanLine[y];
          for x:=0 to Result.Width-1 do
          begin
            SwapBytes(ScanLine[x].B1, ScanLine[x].B3);
          end;
        end;
      end;

    finally Jpg.Free;
    end;
  finally F.Free;
  end;
end;

procedure TForm9.FormCreate(Sender: TObject);
var B: TBitmap;
begin
  B := LoadJpegIntoBitmap('C:\Users\Cosmin Prund\Downloads\ABrownImage.jpg');
  try
    Image1.Picture.Assign(B);
  finally B.Free;
  end;
end;

end.
...