【问题标题】:Converting Jpeg images to Bmp - some images come out blue将 Jpeg 图像转换为 Bmp - 一些图像变为蓝色
【发布时间】:2012-02-13 11:41:24
【问题描述】:

Delphi 似乎不喜欢一些 Jpg 图像。我正在加载的文件似乎是特定的。过程很简单 - a) 将 Jpg 图像加载到 TJpegImage,b) 将 Jpg 对象分配给 TBitmap 对象,以及 c) 保存和/或显示 Bmp 图像。出于某种原因,这些图片总是带有蓝色调。

这些图像在我加载它们的任何地方和任何地方都能完美显示(Windows 图片查看器、绘画、photoshop 等)。

而我做的事情很简单……

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 中存在此问题。至少 XE2 中的 TImage 控件可以正确显示它(而不是旧的两个),但是如果 TBitmap 仍然不起作用,这并不重要。这个文件有什么问题?和/或,Delphi 的渲染有什么问题?

添加信息

我最近发现了一些关于这些图片的信息。当它们来自供应商(产品图片)时,它们是 CMYK 格式。当时,Delphi 7 没有正确支持这些文件(访问冲突和坏图像),因此所有图片都通过转换器过滤为 RGB 颜色格式。许多原始图像也是 TIFF 并转换为 JPG。所以看来FastStone Image Resizer软件在通过时一定不能正确保存这些文件。蓝色图像不会出现在所有人身上,只是一次随机出现一些批次。该软件处理数千种产品,因此有数千种可能的图片。

【问题讨论】:

  • 这似乎是 Delphi JPEG 解码器实现的一个错误,过去我也遇到了一些 jpeg 图像的另一个问题。我的建议是使用特定于 jpeg 图像的组件,例如 NativeJpg v.1.30
  • @CosminPrund FWIW ImageMagick 是为此选择的工具。这是命令行中的单行代码:for /f %f in (*.jpg) do @convert %f %~nf.bmp
  • Delphi 无法检测所有类型的 JPG 图像(CYMK、RGB 是其中的 2 种),这会导致转换时出现错误的调色板。恐怕你会需要 RRUZ 的评论中的 NativeJPG 之类的东西
  • 链接的图像是 JPG,没有使用 sRGB 颜色空间嵌入颜色配置文件,至少 Gimp 是这样告诉我的。如果这是真的,那是一个非常普通的 JPG 文件。它在 Delphi 中呈现蓝色而在 PictureViewer 中呈现绿色的事实告诉我 JPG 文件中有一个微妙的错误。我怀疑Delphi不支持实际格式,它更像是文件中有一个错误,其他解码器比Delphi恢复得更好。正因为如此,我不相信一个小的 JPG 解码器实现,不能保证它更健壮。我会寻找一个广泛传播的图书馆,仅此而已。
  • 使用 D7 jpeg 时,不仅调色板,而且 32 位颜色深度也失败了。

标签: delphi image-processing graphics jpeg


【解决方案1】:

在您的其他问题的提示下,这里有一些代码可将 JPG 文件加载到位图并有条件地对生成的位图应用更正。请注意,这适用于您的 Brown.JPG 图像,但我不知道前 18 个字节中的内容,所以我不知道这是否会长期有效。我个人更喜欢使用现成的、可工作的、广泛使用的库。或者,如果可用,我会使用 David 的想法,即使用 WIC,如果不可用,则恢复为这种风格的 hacky 代码。

这是完整单位代码,因此您可以看到所有使用的单位。表单只需要一个名为Image1TImage,因此您可以先创建表单,将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.

【讨论】:

    【解决方案2】:

    您的文件是蓝色的原因是因为编码是 BGR 而不是 RGB。
    如果您修改jpeg.pas 源文件并使用像素交换(删除TJPEGImage.GetBitmap 中的{.$IFDEF JPEGSO}),您将看到您的示例文件正确地变成棕色。

    所以,我想底线是股票 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 可以在 C:...\RAD Studio\8.0\source\vcl 中找到 (XE),而 C 文件位于 jpg 子文件夹中。

    如果您准备打赌所有具有 RGB 颜色空间的 Adob​​e 文件都需要交换它们的位,您可以轻松破解 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
    

    【讨论】:

    • 精湛的发现,+1 :D 虽然 TWICImage 是我多年来一直梦想的东西,但由于上面大卫的回答,终于找到了它。感谢您提供宝贵的提示!
    • @大卫。我也这么认为,但不知何故我不确定它会产生多大影响......除非它可能是 FireMonkey 方面的问题。
    • 我们从哪里获得 jpeg.pas?另外,我认为这可能是倒退的。图像是 Adob​​e JPEG,即 RGB。可能 Delphi 期待 BGR。
    • 如果你能告诉我如何破解 Delphi XE2 中的Jpeg 单元,我会考虑并尝试一下。它在源中不存在 - 无法访问。也许他们知道它有问题并且不希望人们破解它并改变它?
    • (好的)事情是 FireMonkey 对同一个文件没有问题,它通常是棕色的。
    【解决方案3】:

    我发现了这个问题。这很可能是 Delphi 中的一个错误。

    提供的图像是一种名为 Adob​​e JPEG 的 JPEG 文件的特殊格式。 Adobe JPEG 最特别的地方可能是它允许以 RGB 格式存储图像,尽管它也允许其他格式。大多数 JPEG 是 JFIF 或 EXIF 格式,不使用 RGB。

    当复制 RGB 数据时,无论 Delphi 在做什么,它都会在将红色和蓝色数据加载到画布上时反​​转它。它将其加载为 BGR 而不是 RGB。这可能是因为 Windows(24 位和 32 位)DIB (BMP) 以 BGR 格式存储。

    我猜测对于任何 RGB JPEG,该错误都会出现在 Delphi 中。由于大多数 JPEG 不使用 RGB,因此该错误的发生率很低。如果您有 JPEG 单元的来源,那么简单的解决方法是在加载 RGB JPEG 时颠倒顺序。

    如果您没有来源,请继续。

    Adobe JPEG 以这样的格式(十六进制)43 11 00 47 11 00 42 11 00 指定颜色的顺序,在十六进制编辑器R..G..B 中看起来像这样。如果你在此处通过十六进制编辑器反转RB,它在Windows 中显示错误,在Delphi 中正确。

    要识别 Adob​​e JPEG,前四个字节是(十六进制)FF D8 FF EDFF D8 FF EEEDEE 是区分字节。所有 JPEG 文件都以 FF D8 FF 开头。

    在这些字节之后是表示类型标记长度的两个字节,接着是(在 ASCII 中)Adobe,接着是另外六个字节(表示版本等),最后,(第 18 个字节)是指定格式的字节。 0 表示 RGB。因此,请检查这些重要字节,然后采取相应措施。

    您必须反转文件头中的 RGB 顺序(以欺骗 Delphi),或将其复制到 TBitmap 并使用 ScanLine 将 RGB 反转为正确的顺序。

    格式细节来自阅读 C 中的libJPEG 源代码。

    【讨论】:

    • 不是不同意,而是好奇……您如何确定 Adob​​e 的非标准格式(用您自己的话说,“特殊事物”)构成 Delphi 中的错误?如果“大多数 JPEG 是 JFIF 或 EXIF”,而 Adob​​e 做了一些不同的事情,这是否意味着 Adob​​e JPEG 中存在错误?
    • @Ken White JPEG 标准没有指定颜色格式,因此有人提出了大多数 JPEG 都符合的 JFIF 标准。 Adobe JPEG 虽然丑陋,但仍符合 JPEG 标准。
    • @onemasse,这可能意味着 Adob​​e 的实现中没有错误,只是因为它不典型,但这也不意味着 Delphi 中存在 错误,因为 Adob​​e 没有- 不支持典型格式;这就是我要说的。
    • 我不同意这个错误是在 Delphi 中,因为 QuickTime PictureViewer 也有相同文件的问题。 OTOH,Adobe 以随意使用标准而闻名,以至于您在 libJPEG 中有特殊标志来表示 ADOBE 文件和此评论:/* Unfortunately, some bozo at Adobe saw no reason to be bound by the standard; * the PostScript DCT filter can emit files with many more than 10 blocks/MCU.[...] * we strongly discourage changing C_MAX_BLOCKS_IN_MCU; just because Adobe * sometimes emits noncompliant files doesn't mean you should too. */
    • Adobe 的特殊标志是:boolean write_Adobe_marker; /* should an Adobe marker be written? */boolean saw_Adobe_marker; /* TRUE iff an Adobe APP14 marker was found */UINT8 Adobe_transform; /* Color transform code from Adobe marker */。在 jpeg.pas 中的 Delphi 端:jpeg_compress_struct.write_Adobe_marker : LongBool; { should an Adobe marker be written? }jpeg_decompress_struct.saw_Adobe_marker : LongBool; { TRUE iff an Adobe APP14 marker was found }jpeg_decompress_struct.Adobe_transform : UINT8; { Color transform code from Adobe marker }
    【解决方案4】:

    WIC(适用于 XP 及更高版本)可以处理此图像。该组件在 Delphi 2010 及更高版本中得到了很好的包装。对于早期的 Delphi 版本,使用 COM 接口调用 WIC 很容易。

    这是我的概念验证代码:

    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,000,可能还有一份工作,但这不取决于我:P 非常感谢大卫!
    • 非常酷。有人知道如果你在 XP 上运行它而不安装 WIC 会发生什么吗?
    • 所以我想这证明这不是 Delphi 错误“本身”,而是在没有实现它现在应该处理的标准的“Adobe 解释”时缺少的功能。
    猜你喜欢
    • 1970-01-01
    • 2019-12-03
    • 1970-01-01
    • 1970-01-01
    • 2020-03-21
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多