четверг, 10 июля 2014 г.

ImageEn vs. PNG

Есть такая чудная библиотека - 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;

"Это", простите за резкое слово - "это" НЕ КОД, а говно.

Простите уж. Но Всё надо называть СВОИМИ ИМЕНАМИ.

"Это" - НЕ ЧЕЛОВЕКОЧИТАБЕЛЬНО.

А ещё кто-то "пенял мне" про "примеси".

Уж лучше "примеси", чем такое говно.

Есть ведь люди, которые ЯВНО обладают АНАЛИТИЧЕСКИМ и МАТЕМАТИЧЕСКИМ складом ума, но пишут говно-код (который в ЦЕЛОМ работает).

Я ВСЕГДА удивляюсь этому.

Потому, что я - РЕМЕСЛЕННИК.

Комментариев нет:

Отправить комментарий