Есть такая чудная библиотека - ImageEn.
Мы ею пользуемся. Для многого. Связанного с графикой.
Руки дошли до её "вдумчивого тестирования". Ибо "жизнь заставила".
Так вот сложилось интересное наблюдение - если при помощи ImageEn мы из ОДНОГО и того же TBitmap делаем PNG (на диске скажем), то результат РАЗ ОТ РАЗУ - ОТЛИЧАЕТСЯ.
Даже ImageMagic со значением Fuzz в 20% "ловит отличия".
При этом если мы на этот TBitmap накладываем альфа-канал, то результат становится СТАБИЛЬНЫМ.
Обтрассировался их исходного кода (часть которого лишь в Obj-файлах), в чём дело - пока не понял.
Да и что характерно - файлы лишь "бинарно" отличаются, а вот "на глаз" - картинки ОДИНАКОВЫЕ.
Т.е. явно там какой-то "незначащий мусор". Может тот самый "недостающий" альфа-канал?
TPNGObject - ещё хуже себя ведёт.
Для ImageEn код такой:
А вот ТАК и PNG работает СТАБИЛЬНО (с ТОЧНОСТЬЮ до БАЙТА):
И ещё там в "кишках" Obj-файлов линкуется метод _malloc.
Который выглядел так:
Когда я его переписал так:
-- стало "чуть лучше".
Явно они где-то по памяти "гадят".
Что ещё хочу сказать? А банально.. У парней, которые писали ImageEn - ЯВНО НЕТУ ТЕСТОВ.
Даже АТОМАРНЫХ... Не говоря ух об интеграционных или модульных.
Ну и потом. Вот "это":
"Это", простите за резкое слово - "это" НЕ КОД, а говно.
Простите уж. Но Всё надо называть СВОИМИ ИМЕНАМИ.
"Это" - НЕ ЧЕЛОВЕКОЧИТАБЕЛЬНО.
А ещё кто-то "пенял мне" про "примеси".
Уж лучше "примеси", чем такое говно.
Есть ведь люди, которые ЯВНО обладают АНАЛИТИЧЕСКИМ и МАТЕМАТИЧЕСКИМ складом ума, но пишут говно-код (который в ЦЕЛОМ работает).
Я ВСЕГДА удивляюсь этому.
Потому, что я - РЕМЕСЛЕННИК.
Мы ею пользуемся. Для многого. Связанного с графикой.
Руки дошли до её "вдумчивого тестирования". Ибо "жизнь заставила".
Так вот сложилось интересное наблюдение - если при помощи ImageEn мы из ОДНОГО и того же TBitmap делаем PNG (на диске скажем), то результат РАЗ ОТ РАЗУ - ОТЛИЧАЕТСЯ.
Даже ImageMagic со значением Fuzz в 20% "ловит отличия".
При этом если мы на этот TBitmap накладываем альфа-канал, то результат становится СТАБИЛЬНЫМ.
Обтрассировался их исходного кода (часть которого лишь в Obj-файлах), в чём дело - пока не понял.
Да и что характерно - файлы лишь "бинарно" отличаются, а вот "на глаз" - картинки ОДИНАКОВЫЕ.
Т.е. явно там какой-то "незначащий мусор". Может тот самый "недостающий" альфа-канал?
TPNGObject - ещё хуже себя ведёт.
Для ImageEn код такой:
{$IfDef nsTest}
l_Bmp.Bitmap.SaveToStream(l_Out); // - тут СТАБИЛЬНОЕ совпадение
{$Else nsTest}
l_IO := TImageEnIO.Create(nil);
try
//l_IO.AttachedBitmap := l_Bmp.Bitmap;
l_IO.Bitmap := l_Bmp.Bitmap;
l_IO.Params.BitsPerSample := 8;
l_IO.Params.SamplesPerPixel := 1;
{$IfDef nsTest}
l_IO.SaveToStreamBMP(l_Out); // - тут ИНОГДА несовпадение с порогом в 3%
{$Else nsTest}
l_IO.SaveToStreamPNG(l_Out); // - тут ЧАСТО несовпадение с порогом даже БОЛЬШЕ 20%
{$EndIf nsTest}
Хотя ПОВТОРЮ - ВИЗУАЛЬНО отличий не видно.
А вот ТАК и PNG работает СТАБИЛЬНО (с ТОЧНОСТЬЮ до БАЙТА):
const
c_ColWhite: TRGB = (b:255; g:255; r:255);
...
l_Bitmap.SaveToStream(l_Stream);
l_Stream.Seek(0, 0);
with TImageEnIO.Create(nil) do
try
LoadFromStreamBMP(l_Stream);
l_Stream.Seek(0, 0);
l_Stream.Size:= 0;
// создаём альфа-канал. потому что НАДО рисовать формулы
// уже с альфа-каналом,
IEBitmap.CreateAlphaChannel;
for J := 0 to IEBitmap.Width - 1 do
for K := 0 to IEBitmap.Height - 1 do
if EqualRGB(IEBitmap.Pixels[J,K], c_ColWhite) then
IEBitmap.Alpha[J,K] := 0
else
IEBitmap.Alpha[J,K] := 255;
SaveToStreamPNG(l_Stream);
Да!И ещё там в "кишках" Obj-файлов линкуется метод _malloc.
Который выглядел так:
function _malloc(aSize: Integer): Pointer; begin GetMem(Result, aSize); end;
Когда я его переписал так:
function _malloc(aSize: Integer): Pointer; begin GetMem(Result, aSize); if (Resut <> nil) then FillChar(Result, aSize, 0); end;
-- стало "чуть лучше".
Явно они где-то по памяти "гадят".
Что ещё хочу сказать? А банально.. У парней, которые писали ImageEn - ЯВНО НЕТУ ТЕСТОВ.
Даже АТОМАРНЫХ... Не говоря ух об интеграционных или модульных.
Ну и потом. Вот "это":
procedure WritePNGStream(Stream:TStream; bitmap:TIEBitmap; var IOParams:TIOParamsVals; var xProgress:TProgressRec; AlphaChannel:TIEMask);
var
png_ptr:png_structp;
info_ptr:png_infop;
Error_ptr:pointer;
bit_depth,color_type,interlace_type:integer;
WBitmap:TIEBitmap;
BackCol,ForeCol:TRGB;
FreeW:boolean; // se true liberare WBitmap
qt:TIEQuantizer;
palette:array [0..255] of TRGB;
ppalette:PRGBROW;
background:png_color_16;
number_passes,pass,y,x,height,width:integer;
px,ppx:pointer;
pp:PRGB;
brow:pbyte;
pw:pword;
nullpr:TProgressRec;
bitmapwidth1:integer;
iodata:TIOData;
px2,px4:PRGBA;
px_byte,px3:pbyte;
bb:byte;
bps:integer;
hasalpha:boolean;
px_word:pword;
p8:png_color_8;
l_SamplesPerByte: Byte;
l_MaxColors: Integer;
function FormRowByteWithPalette(var thePtr: PRGB; var theCounter: Integer): Byte;
var
l_Idx: Byte;
I: Integer;
begin
Result := 0;
for I := 1 to l_SamplesPerByte do
begin
Result := Result shl bit_depth;
l_Idx := qt.RGBIndex[thePtr^];
Result := Result or l_Idx;
Inc(thePtr);
Inc(theCounter);
end;
end;
begin
with nullpr do begin
Aborting:=xProgress.Aborting;
fOnProgress:=nil;
Sender:=nil;
end;
Error_ptr:=nil;
png_ptr := png_create_write_struct('1.2.5',Error_ptr,@ErrorFunc, @WarnFunc);
if png_ptr=nil then
raise EInvalidGraphic.create('Error on creating PNG');
info_ptr := png_create_info_struct(png_ptr);
if info_ptr=nil then begin
png_destroy_write_struct(@png_ptr, nil);
raise EInvalidGraphic.create('Error on creating PNG');
end;
iodata.Stream:=Stream;
iodata.Aborting:=xProgress.Aborting;
png_set_write_fn(png_ptr,@iodata,@WriteFunc,@FlushFunc);
// converts b/w bitmap to pf24bit regards of IOParams fields
// The bitmap to write will be contained in WBitmap
FreeW:=false;
if (IOParams.BitsPerSample=1) then begin
// required to save to b/w
if Bitmap.pixelformat=ie1g then
WBitmap:=Bitmap
else begin
// convert to 1 bit
WBitmap:=_ConvertTo1bitEx(Bitmap,BackCol,ForeCol);
if WBitmap=nil then begin
// impossible to convert to 1 bit, convert to ordered dither
WBitmap:=TIEBitmap.Create;
WBitmap.Assign(Bitmap);
WBitmap.PixelFormat:=ie1g;
end;
FreeW:=true;
end;
end else begin
// required to save in true color
if Bitmap.pixelformat=ie1g then begin
// convert to 24 bit
WBitmap:=TIEBitmap.Create;
WBitmap.Assign(Bitmap);
WBitmap.PixelFormat:=ie24RGB;
FreeW:=true;
end else
WBitmap:=Bitmap;
end;
// assign interlace_type
if ioparams.PNG_Interlaced then
interlace_type:=PNG_INTERLACE_ADAM7
else
interlace_type:=PNG_INTERLACE_NONE;
// assign bit_depth and color_type
if ioparams.SamplesPerPixel=1 then begin
// B/W or palette
if wbitmap.pixelformat=ie1g then begin
// B/W
color_type:=PNG_COLOR_TYPE_GRAY;
bit_depth:=1;
end else begin
// palette
color_type:=PNG_COLOR_TYPE_PALETTE;
bit_depth:=ioparams.BitsPerSample;
end;
end else begin
// true color
color_type:=PNG_COLOR_TYPE_RGB;
bit_depth:=8;
end;
hasalpha:=assigned(AlphaChannel) and (not AlphaChannel.Full);
if hasalpha and (color_type=PNG_COLOR_TYPE_RGB) then
color_type:=color_type or PNG_COLOR_MASK_ALPHA;
//
// Create palette if needed
if (ioparams.SamplesPerPixel=1) and (ioparams.BitsPerSample>1) and (wbitmap.pixelformat<>ie1g) then begin
if hasalpha then begin
// save alpha channel as an entry in the color map (here 16 bit gray isn't supported)
bit_depth:=8;
bps:=1 shl ioparams.BitsPerSample;
qt:=TIEQuantizer.Create(wBitmap, palette, imin(bps,255), -1, 0); // entry 0 reserved for transparent layer
getmem(ppalette,256*sizeof(TRGB));
copymemory(@(ppalette[1]),@(palette[0]),255*sizeof(TRGB));
with ppalette[0] do begin
r:=ioparams.PNG_Background.r;
g:=ioparams.PNG_Background.g;
b:=ioparams.PNG_Background.b;
end;
for x:=0 to 255 do
bswap(ppalette^[x].r,ppalette^[x].b);
png_set_PLTE(png_ptr,info_ptr,png_colorp(ppalette),imin(bps+1,256));
if hasalpha then begin
bb:=0;
png_set_tRNS(png_ptr,info_ptr,@bb,1,nil);
end;
end else begin
// do not save alpha. Full gray and 16 bit gray supported
l_MaxColors := 1 shl bit_depth;
if l_MaxColors > 256 then
l_MaxColors := 256;
qt:=TIEQuantizer.Create(wBitmap, palette, l_MaxColors, -1, 0);
getmem(ppalette,l_MaxColors*sizeof(TRGB));
copymemory(@(ppalette[0]),@(palette[0]),l_MaxColors*sizeof(TRGB));
for x:=0 to l_MaxColors-1 do
bswap(ppalette^[x].r,ppalette^[x].b);
if not qt.GrayScale then
png_set_PLTE(png_ptr,info_ptr,png_colorp(ppalette),l_MaxColors)
else
color_type:=PNG_COLOR_TYPE_GRAY;
end;
end else begin
qt:=nil;
ppalette:=nil;
end;
png_set_IHDR(png_ptr,info_ptr,bitmap.width,bitmap.height,bit_depth,color_type,
interlace_type,PNG_COMPRESSION_TYPE_DEFAULT,PNG_FILTER_TYPE_DEFAULT);
// DPI
png_set_pHYs(png_ptr,info_ptr,round(ioparams.DPIX*100/2.54),round(ioparams.DPIY*100/2.54),PNG_RESOLUTION_METER);
//
case ioparams.PNG_Filter of
ioPNG_FILTER_NONE : png_set_filter(png_ptr, 0, PNG_FILTER_NONE);
ioPNG_FILTER_SUB : png_set_filter(png_ptr, 0, PNG_FILTER_SUB);
ioPNG_FILTER_PAETH : png_set_filter(png_ptr, 0, PNG_FILTER_PAETH);
end;
// set background
zeromemory(@background,sizeof(background));
if assigned(qt) then
background.index:=qt.RGBIndex[ioparams.PNG_Background]
else begin
background.red:=ioparams.PNG_Background.r shl 8;
background.green:=ioparams.PNG_Background.g shl 8;
background.blue:=ioparams.PNG_Background.b shl 8;
end;
png_set_bKGD(png_ptr,info_ptr,@background);
//
png_set_compression_level(png_ptr,ioparams.PNG_Compression);
//
png_set_bgr(png_ptr);
//
png_write_info(png_ptr,info_ptr);
// write rows
number_passes := png_set_interlace_handling(png_ptr);
height:=wbitmap.height;
width:=wbitmap.width;
xProgress.per1:=100/(height*number_passes);
xProgress.val:=0;
px2:=nil;
if (color_type=PNG_COLOR_TYPE_PALETTE) or (color_type=PNG_COLOR_TYPE_GRAY) then
getmem(px,wbitmap.width*imax(1,bit_depth div 8));
if (color_type and PNG_COLOR_MASK_ALPHA)<>0 then
getmem(px2,wbitmap.width*sizeof(TRGBA));
l_SamplesPerByte := 8 div bit_depth;
for pass := 0 to number_passes-1 do begin
for y := 0 to height-1 do begin
if (color_type and PNG_COLOR_MASK_PALETTE)<>0 then begin
// palette
brow:=px;
pp:=wbitmap.scanline[y];
px_byte:=pbyte(pp);
bitmapwidth1:=wbitmap.width-1;
if assigned(AlphaChannel) and (not AlphaChannel.Full) then begin
// alpha channel
px3:=alphachannel.scanline[y];
for x:=0 to bitmapwidth1 do begin
if px3^<255 data-blogger-escaped-1="" data-blogger-escaped-:="FormRowByteWithPalette(pp," data-blogger-escaped-and="" data-blogger-escaped-begin="" data-blogger-escaped-bitmapwidth1="" data-blogger-escaped-brow="" data-blogger-escaped-case="" data-blogger-escaped-color_type="" data-blogger-escaped-do="" data-blogger-escaped-else="" data-blogger-escaped-end="" data-blogger-escaped-for="" data-blogger-escaped-ie24rgb:="" data-blogger-escaped-ie8p:="" data-blogger-escaped-if="" data-blogger-escaped-inc="" data-blogger-escaped-of="" data-blogger-escaped-palette="" data-blogger-escaped-png_color_mask_color="" data-blogger-escaped-png_ptr="" data-blogger-escaped-png_write_rows="" data-blogger-escaped-pp="" data-blogger-escaped-px3="" data-blogger-escaped-px="" data-blogger-escaped-px_byte="" data-blogger-escaped-qt.rgbindex="" data-blogger-escaped-simple="" data-blogger-escaped-then="" data-blogger-escaped-to="" data-blogger-escaped-wbitmap.pixelformat="" data-blogger-escaped-while="" data-blogger-escaped-x:="0" data-blogger-escaped-x="">0 then begin
// truecolor
if (color_type and PNG_COLOR_MASK_ALPHA)<>0 then begin
// alpha channel
pp:=wbitmap.scanline[y];
px3:=alphachannel.scanline[y];
px4:=px2;
for x:=0 to width-1 do begin
with px4^ do begin
r:=pp^.r;
g:=pp^.g;
b:=pp^.b;
a:=px3^;
end;
inc(pp);
inc(px3);
inc(px4);
end;
png_write_rows(png_ptr, @px2, 1);
end else begin
ppx:=wbitmap.scanline[y];
png_write_rows(png_ptr, @ppx, 1);
end;
end else if (color_type=PNG_COLOR_TYPE_GRAY) then begin
// gray scale
if bit_depth=16 then begin
case wbitmap.pixelformat of
ie24RGB:
begin
pp:=wbitmap.scanline[y];
pw:=px;
for x:=0 to width-1 do begin
pw^:=qt.RGBIndex[pp^];
inc(pw);
inc(pp);
end;
end;
ie16g:
begin
px_word:=wbitmap.scanline[y];
pw:=px;
for x:=0 to width-1 do begin
pw^:=px_word^ shr 8;
inc(pw);
inc(px_word);
end;
end;
end;
png_write_rows(png_ptr, @px, 1);
end else if bit_depth=8 then begin
case wbitmap.pixelformat of
ie24RGB:
begin
pp:=wbitmap.scanline[y];
brow:=px;
for x:=0 to width-1 do begin
brow^:=qt.RGBIndex[pp^];
inc(brow);
inc(pp);
end;
end;
ie8g:
begin
px_byte:=wbitmap.scanline[y];
brow:=px;
for x:=0 to width-1 do begin
brow^:=px_byte^;
inc(brow);
inc(px_byte);
end;
end;
end;
png_write_rows(png_ptr, @px, 1);
end
else
begin
if (bit_depth < 8) and ((wbitmap.pixelformat = ie24RGB)) then
begin
pp := wbitmap.scanline[y];
brow := px;
x := 0;
while x < width-1 do
begin
brow^ := FormRowByteWithPalette(pp, x);
inc(brow);
end;
png_write_rows(png_ptr, @px, 1);
end
else
begin
ppx:=wbitmap.scanline[y];
png_write_rows(png_ptr, @ppx, 1);
end;
end;
end;
// OnProgress
with xProgress do begin
inc(val);
if assigned(fOnProgress) then
fOnProgress(Sender,trunc(per1*val));
end;
if xProgress.Aborting^ then
break;
end;
if xProgress.Aborting^ then
break;
end;
if (color_type and PNG_COLOR_MASK_ALPHA)<>0 then
freemem(px2);
if (color_type=PNG_COLOR_TYPE_PALETTE) or (color_type=PNG_COLOR_TYPE_GRAY) then
freemem(px);
// fine
if not xProgress.Aborting^ then
png_write_end(png_ptr,info_ptr);
if ppalette<>nil then
freemem(ppalette);
png_destroy_write_struct(@png_ptr,@info_ptr);
if FreeW then
WBitmap.free;
if assigned(qt) then
qt.free;
end;
"Это", простите за резкое слово - "это" НЕ КОД, а говно.
Простите уж. Но Всё надо называть СВОИМИ ИМЕНАМИ.
"Это" - НЕ ЧЕЛОВЕКОЧИТАБЕЛЬНО.
А ещё кто-то "пенял мне" про "примеси".
Уж лучше "примеси", чем такое говно.
Есть ведь люди, которые ЯВНО обладают АНАЛИТИЧЕСКИМ и МАТЕМАТИЧЕСКИМ складом ума, но пишут говно-код (который в ЦЕЛОМ работает).
Я ВСЕГДА удивляюсь этому.
Потому, что я - РЕМЕСЛЕННИК.
Комментариев нет:
Отправить комментарий