Преобразование изображений Jpeg в Bmp - некоторые изображения выделяются синим цветом

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

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

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

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 не должно правильно сохранять эти файлы, когда они проходят. Синее изображение не происходит на всех из них, просто несколько случайных партий за раз. Программное обеспечение обрабатывает тысячи продуктов, поэтому есть тысячи возможных изображений.

Ответ 1

Я понял вопрос. Это, скорее всего, ошибка в 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 задает порядок цветов в таком формате (в Hex) 43 11 00 47 11 00 42 11 00, который выглядит так в шестнадцатеричном редакторе R..G..B. Если вы отмените R и B здесь с помощью шестнадцатеричного редактора, в Windows это будет неправильно, и прямо в Delphi.

Чтобы распознать Adobe JPEG, первые четыре байта являются либо (в Hex) 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.

Ответ 2

Причина, по которой ваш файл синий, заключается в том, что кодировка BGR isntead 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

Ответ 3

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 было установлено к настоящему времени.

Ответ 4

Подсказка по вашему другому вопросу, здесь некоторый код для загрузки 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.