My hobby project (image-to-character) lazarus / 39-Graphics ---------------------------------------------------------------------- anyone · 2020-10-04 16:32 Hello everybody. I have a hobby project that I am reluctant to upload to GitHub or make an entry in Free Pascal wiki page. Anyway, thanks to the fcl-image example code in FPC folder, I managed to do conversion of image (JPEG in this case) to 16-color character in text mode. I did it in C# .NET before but now I am glad that I am able to port it to Free Pascal. But I wish to know: 1) How to support reading of image types without defining the Reader (JPEG, BMP, GIF, PNG...) 2) Is there any built-in support for color quantization from RGB 24-bit down to 16 color? Actually for the second question, I had my own algorithm (written 20 years ago), but just want to know if Free Pascal libraries have any. Your feedback or criticism is welcomed. UPDATE (as of 5-Oct-2020 6:55PM UTC+2): A big "Thank You" to @wp for his /her kind effort  to fix my "img2chr.pp" program so that it now supports multiple image file format regardless of the incorrect file extension. Code: Pascal  [Select][+][-]window.onload = function(){var x1 = document.getElementById("main_content_section"); if (x1) { var x = document.getElementsByClassName("geshi");for (var i = 0; i {$mode objfpc}{$h+}uses fpreadgif, fpreadpng, fpreadbmp, fpreadjpeg, fpimage, classes, sysutils, Crt; var img : TFPMemoryImage;    ReadFile : string;    pixel : TFPColor;    x, y:integer;    ratioX, ratioY:integer; function SimplifyColorComponent(Value:byte):byte;begin if Value>=52 then   SimplifyColorComponent:=63 else if Value>=32 then   SimplifyColorComponent:=42 else if Value>=12 then   SimplifyColorComponent:=21 else   SimplifyColorComponent:=0;end; function DecreaseColor256(Red,Green,Blue:byte):byte;const Palette16:array [0..15,1..3] of byte=((0,0,0),(0,0,42),(0,42,0),(0,42,42),                                     (42,0,0),(42,0,42),(42,42,0),(42,42,42),                                     (0,0,21),(0,0,63),(0,42,21),(0,42,63),                                     (42,0,21),(42,0,63),(42,42,21),(42,42,63));var Color,Component, Value:byte; NewRed,NewGreen,NewBlue:byte; begin DecreaseColor256:=0; C omponent:=1;  repeat   case Component of     1:Value:=SimplifyColorComponent(Red div 4);     2:Value:=SimplifyColorComponent(Green div 4);     3:Value:=SimplifyColorComponent(Blue div 4);   end;    Color:=0;    while Value<>Palette16[Color,Component] do   begin     Inc(Color);    if Color>15 then     begin       Dec(Value,21);       Color:=0;     end;   end;    case Component of     1:NewRed:=Value;     2:NewGreen:=Value;     3:NewBlue:=Value;   end;    Inc(Component);  until Component>3;  for Color:=0 to 15 do   if (Palette16[Color,1]=NewRed) and (Palette16[Color,2]=NewGreen)   and (Palette16[Color,3]=NewBlue) then   begin     DecreaseColor256:=Color;     Exit;   end;end; procedure Init;begin  ReadFile := ParamStr(1);  img := TFPMemoryImage.Create(0,0);end; procedure ReadImage;var  color16, r, g, b:byte;  readerClass: TFPCustomImageReaderClass;  reader: TFPCustomImageReader;  stream: TStream;begin   if FileExists(ReadFile) then   begin     stream := TFileStream.Create(ReadFile, fmOpenRead + fmShareDenyNone);     try       stream.Position := 0;       readerClass := TFPCustomImage.FindHandlerFromStream(stream).Reader;     if Assigned(readerClass) then begin         reader := readerClass.Create;         try           img.LoadFromStream(stream, reader);         finally           reader.Free;         end;          ratioY := img.Height div 30;         ratioX := img.Width div 90;        y:=0;         x:=0;          while (y');  end  else    try      Init;    ReadImage;      Dispose;    except      on e : exception do        writeln ('Error: ',e.message);    end;   ReadLn;end. Example screen shot is attached together. (Left: output of this img2chr program using .... Right: original Panda.jpg photo) ---------------------------------------------------------------------- MarkMLl · 2020-10-04 19:59 Quote from: anyone on October 04, 2020, 06:32:52 pm1) How to support reading of image types without defining the Reader (JPEG, BMP, GIF, PNG...) I think you'd have to open it as a binary file and look to see if the first few bytes match a known fingerprint. On unix there is a program called  file  which uses (or at least used to use) what is known as the "magic database", that contains fingerprints for a large number of files based on (a) bytes at a known location relative to the start (i.e. in a header) and (b) bytes whose location in the file is given by indirection via a header field. Obviously you don't need that amount of flexibility for a program handling a limited number of filetypes. It looks as though these days the magic database doesn't come as a simple text format, but https://en.wikipedia.org/wiki/List_of_file_signatures is a good alternative. MarkMLl ---------------------------------------------------------------------- Handoko · 2020-10-05 02:36 Quote from: anyone on October 04, 2020, 06:32:52 pm1) How to support reading of image types without defining the Reader (JPEG, BMP, GIF, PNG...) I think there is no simple solution for it. But I will solve the problem like this: Code: Pascal  [Select][+][-]window.onload = function(){var x1 = document.getElementById("main_content_section"); if (x1) { var x = document.getElementsByClassName("geshi");for (var i = 0; i var  Reader : TFPCustomImageReader;  FileName: string;  Sig: array of char;  S: string;begin   if ParamCount <= 0 then  begin    WriteLn('Usage: img2chr ');    Halt;  end;   Filename := ParamStr(1);  Reader := nil;   // Open based on file extension  S :=  ExtractFileExt(Filename.ToLower);  if S = '.jpg' then TryOpenAsJPG(Reader, Filename)  else    if S = '.png' then TryOpenAsPNG(Reader, Filename)    else      if S = '.bmp' then TryOpenAsBMP(Reader, Filename)      else        if S = '.gif' then TryOpenAsGIF(Reader, Filename));   // Open based on magic number  if not(Assigned(Reader) then  begin    Sig := ReadFileSignature(Filename);    If SignatureIsJPG then TryOpenAsJPG(Reader, Filename)    else      if SignatureIsPNG then TryOpenAsPNG(Reader, Filename)      else        if SignatureIsBMP then TryOpenAsBMP(Reader, Filename)        else           if SignatureIsGIF then TryOpenAsGIF(Reader, Filename)  end;   if not(Assgined(Reader)) then  begin    WriteLn('Unrecognizable file format.');    Halt;  end; end; You will need write all the TryOpenAs* and SignatureIs* and ReadFileSignature functions. ---------------------------------------------------------------------- PascalDragon · 2020-10-05 07:17 All fpReadXXX functions register their reader in the global ImageHandlers of unit fpImage and TFPCustomImage makes use of that. So simply doing a TFPMemoryImage.LoadFromFile without any reader should work already. ---------------------------------------------------------------------- Fred vS · 2020-10-05 08:42 Hello. You can detect the format with BGRABitmap using DetectFileFormat functions of BGRABitmapTypes unit.   {** Detect the file format of a given file }   Code: Pascal  [Select][+][-]window.onload = function(){var x1 = document.getElementById("main_content_section"); if (x1) { var x = document.getElementsByClassName("geshi");for (var i = 0; i function DetectFileFormat(AFilenameUTF8: string): TBGRAImageFormat   {** Detect the file format of a given stream. ''ASuggestedExtensionUTF8'' can be provided to guess the format }  Code: Pascal  [Select][+][-]window.onload = function(){var x1 = document.getElementById("main_content_section"); if (x1) { var x = document.getElementsByClassName("geshi");for (var i = 0; i  function DetectFileFormat(AStream: TStream; ASuggestedExtensionUTF8: string = ''): BGRAImageFormat;   {** Returns the file format that is most likely to be stored in the given filename (according to its extension) } Code: Pascal  [Select][+][-]window.onload = function(){var x1 = document.getElementById("main_content_section"); if (x1) { var x = document.getElementsByClassName("geshi");for (var i = 0; i   function SuggestImageFormat(AFilenameOrExtensionUTF8: string): TBGRAImageFormat;   {** Returns a likely image extension for the format }   Code: Pascal  [Select][+][-]window.onload = function(){var x1 = document.getElementById("main_content_section"); if (x1) { var x = document.getElementsByClassName("geshi");for (var i = 0; i function SuggestImageExtension(AFormat: TBGRAImageFormat): string;   {** Create an image reader for the given format } Code: Pascal  [Select][+][-]window.onload = function(){var x1 = document.getElementById("main_content_section"); if (x1) { var x = document.getElementsByClassName("geshi");for (var i = 0; i   function CreateBGRAImageReader(AFormat: TBGRAImageFormat): FPCustomImageReader;   {** Create an image writer for the given format. ''AHasTransparentPixels'' specifies if alpha channel must be supported }  Code: Pascal  [Select][+][-]window.onload = function(){var x1 = document.getElementById("main_content_section"); if (x1) { var x = document.getElementsByClassName("geshi");for (var i = 0; i  function CreateBGRAImageWriter(AFormat: TBGRAImageFormat; AHasTransparentPixels: boolean): TFPCustomImageWriter; Fre;D PS: Thanks to Circular. ---------------------------------------------------------------------- MarkMLl · 2020-10-05 11:07 Thanks for updating us Fred :-) MarkMLl ---------------------------------------------------------------------- anyone · 2020-10-05 11:28 It is out of my expectation today that I receive many helpful response from fellow coders. @MarkMLI . Thanks, reading the signature of magic number is the universal way of handling file format. @Handoko. Thank you for the sample code (although it is not completed), reading both the file extension and file header signature is certainly a secure way to identify the image file format. But, surprisingly, if I didn't specify the Reader, as suggested by @PascalDragon, it can open multiple image file format (BMP, PNG, JPG, GIF)! However, it failed on one particular PNG file no matter I specify the Reader explicitly as PNG or not. @Fred vS. Thank you for the useful information. BGRABitmap was what I want to try in the first place, though, I found this fcl-image example code instead of BGRABitmap example code in my FPC installation folder. ---- BTW, Has anyone tried to convert image to ASCII art before? To get the grey intensity, can I use back the same code, or use BGRABitmap? Quote from: PascalDragon on October 05, 2020, 09:17:08 amAll fpReadXXX functions register their reader in the global ImageHandlers of unit fpImage and TFPCustomImage makes use of that. So simply doing a TFPMemoryImage.LoadFromFile without any reader should work already. Looks like TFPMemoryImage.LoadFromFile would auto-detect file format? And yes, thank you, it works. ---------------------------------------------------------------------- circular · 2020-10-05 12:57 Quote from: anyone on October 05, 2020, 01:28:17 pmHowever, it failed on one particular PNG file no matter I specify the Reader explicitly as PNG or not.The file might not be in PNG format in fact. QuoteBTW, Has anyone tried to convert image to ASCII art before? To get the grey intensity, can I use back the same code, or use BGRABitmap?It does not really matter at this point. Though with BGRABitmap you could resample the image instead of getting individual pixels separated by some distance. The image may look better with resample. I was wondering about adding ASCII art features to BGRABitmap. Though I was thinking of trying something else: that the shape of the character would try to match the pixels it covers. For example to use "/" for a diagonal line etc. ---------------------------------------------------------------------- MarkMLl · 2020-10-05 13:30 Quote from: circular on October 05, 2020, 02:57:32 pmQuote from: anyone on October 05, 2020, 01:28:17 pmHowever, it failed on one particular PNG file no matter I specify the Reader explicitly as PNG or not.The file might not be in PNG format in fact. I don't have the book that discusses it any more, but note that the sequence of characters at the start of a PNG was carefully chosen to detect the transmission and storage errors which had bedevilled GIFs: lost MSB, endian swaps, disagreement regarding EOL, truncation at CP/M-style EOF and so on. It is possible- I'm not saying it happens, but it's possible- that some of those could be accommodated by promiscuous reader software. So sometimes even if a file appears to be broken, it's worth looking carefully at it. MarkMLl ---------------------------------------------------------------------- anyone · 2020-10-05 13:45 Quote from: circular on October 05, 2020, 02:57:32 pmQuote from: anyone on October 05, 2020, 01:28:17 pmHowever, it failed on one particular PNG file no matter I specify the Reader explicitly as PNG or not.The file might not be in PNG format in fact. That PNG file can be opened by LazPaint nonetheless. (The said PNG file has background transparency) Quote from: circular on October 05, 2020, 02:57:32 pmIt does not really matter at this point. Though with BGRABitmap you could resample the image instead of getting individual pixels separated by some distance. The image may look better with resample. I was wondering about adding ASCII art features to BGRABitmap. Though I was thinking of trying something else: that the shape of the character would try to match the pixels it covers. For example to use "/" for a diagonal line etc. Interesting idea, that is also considered an ASCII art. Besides this, I have a feature suggestion for LazPaint. I am not sure about its usefulness though. If anyone of you remember Paint Shop Pro, there is a feature called "Increase color depth..." and "Decrease color depth...". This feature is called "color quantization" in Python programming. As I have limited knowledge about mathematic calculation, I will leave it to you as to how to implement this feature (if you accept my suggestion). Such as, 24-bit color reduced to 256 color or 16 color....etc. ---------------------------------------------------------------------- anyone · 2020-10-05 14:05 Please find the attached PNG file which could not be opened with fcl- image, but can be opened using LazPaint (and of course, Windows Photo Editor) This is the error message: Code: Pascal  [Select][+][-]window.onload = function(){var x1 = document.getElementById("main_content_section"); if (x1) { var x = document.getElementsByClassName("geshi");for (var i = 0; i C:\FPC>img2chr panda.pngError: Error while reading stream: Wrong image format ---------------------------------------------------------------------- Fred vS · 2020-10-05 14:14 Quote from: anyone on October 05, 2020, 03:45:03 pm If anyone of you remember Paint Shop Pro, there is a feature called "Increase color depth..." and "Decrease color depth...". This feature is called "color quantization" in Python programming. Yes, I remember and I liked that feature. IMHO, increasing color depth will be easy but decreasing is more tricky (maybe with different ways to calculate the average of the new color). But I vote for it. ---------------------------------------------------------------------- wp · 2020-10-05 14:17 Quote from: anyone on October 05, 2020, 04:05:18 pmPlease find the attached PNG file which could not be opened with fcl-image, but can be opened using LazPaint (and of course, Windows Photo Editor) This is the error message: Code: Pascal  [Select][+][-]window.onload = function(){var x1 = document.getElementById("main_content_section"); if (x1) { var x = document.getElementsByClassName("geshi");for (var i = 0; i C:\FPC>img2chr panda.pngError: Error while reading stream: Wrong image formatTrying to open this image with IrfanView I get the message that this is a jpg file with incorrect extension. The LCL TImage (which uses the fcl-image readers/writers) loads the image without any issues. ---------------------------------------------------------------------- Fred vS · 2020-10-05 14:24 Quote from: wp on October 05, 2020, 04:17:16 pmQuote from: anyone on October 05, 2020, 04:05:18 pmPlease find the attached PNG file which could not be opened with fcl-image, but can be opened using LazPaint (and of course, Windows Photo Editor) This is the error message: Code: Pascal  [Select][+][-]window.onload = function(){var x1 = document.getElementById("main_content_section"); if (x1) { var x = document.getElementsByClassName("geshi");for (var i = 0; i C:\FPC>img2chr panda.pngError: Error while reading stream: Wrong image formatTrying to open this image with IrfanView I get the message that this is a jpg file with incorrect extension. Indeed, with ImageMagick I get this (see picture): ---------------------------------------------------------------------- circular · 2020-10-05 14:35 Quote from: anyone on October 05, 2020, 03:45:03 pmBesides this, I have a feature suggestion for LazPaint. I am not sure about its usefulness though. If anyone of you remember Paint Shop Pro, there is a feature called "Increase color depth..." and "Decrease color depth...". This feature is called "color quantization" in Python programming. As I have limited knowledge about mathematic calculation, I will leave it to you as to how to implement this feature (if you accept my suggestion). Such as, 24-bit color reduced to 256 color or 16 color....etc. It is possible to save a file in 256 colors, which makes color quantization. You can as well use the palette toolbar. For example generate 16 colors and then apply dithering with it. The image can still have all 24-bit colors but you can get the effect if you like. ---------------------------------------------------------------------- anyone · 2020-10-05 14:43 Quote from: wp on October 05, 2020, 04:17:16 pm Trying to open this image with IrfanView I get the message that this is a jpg file with incorrect extension. The LCL TImage (which uses the fcl-image readers/writers) loads the image without any issues. After I renamed the panda.png to a new filename with .jpg extension, only then my program can interpret the image correctly... ---------------------------------------------------------------------- anyone · 2020-10-05 14:45 Quote from: Fred vS on October 05, 2020, 04:14:42 pmQuote from: anyone on October 05, 2020, 03:45:03 pm If anyone of you remember Paint Shop Pro, there is a feature called "Increase color depth..." and "Decrease color depth...". This feature is called "color quantization" in Python programming. Yes, I remember and I liked that feature. IMHO, increasing color depth will be easy but decreasing is more tricky (maybe with different ways to calculate the average of the new color). But I vote for it. Aww... thank you for supporting my idea. ---------------------------------------------------------------------- anyone · 2020-10-05 14:49 Quote from: circular on October 05, 2020, 04:35:13 pmQuote from: anyone on October 05, 2020, 03:45:03 pmBesides this, I have a feature suggestion for LazPaint. I am not sure about its usefulness though. If anyone of you remember Paint Shop Pro, there is a feature called "Increase color depth..." and "Decrease color depth...". This feature is called "color quantization" in Python programming. As I have limited knowledge about mathematic calculation, I will leave it to you as to how to implement this feature (if you accept my suggestion). Such as, 24-bit color reduced to 256 color or 16 color....etc. It is possible to save a file in 256 colors, which makes color quantization. You can as well use the palette toolbar. For example generate 16 colors and then apply dithering with it. The image can still have all 24-bit colors but you can get the effect if you like. Is there any programming API for these? It would be nice to use one instead of inventing my own. I did not know that it can be done that way in LazPaint. ---------------------------------------------------------------------- wp · 2020-10-05 14:50 Quote from: anyone on October 05, 2020, 04:43:05 pmQuote from: wp on October 05, 2020, 04:17:16 pm Trying to open this image with IrfanView I get the message that this is a jpg file with incorrect extension. The LCL TImage (which uses the fcl-image readers/writers) loads the image without any issues. After I renamed the panda.png to a new filename with .jpg extension, only then my program can interpret the image correctly... I guess that when you follow PascalDragon's idea with the ImageHandlers (i.e. loading the image without specifying a reader) fcl-image will open the file successfully even when it has a wrong extension. ---------------------------------------------------------------------- anyone · 2020-10-05 14:59 Quote from: wp on October 05, 2020, 04:50:42 pmQuote from: anyone on October 05, 2020, 04:43:05 pmQuote from: wp on October 05, 2020, 04:17:16 pm Trying to open this image with IrfanView I get the message that this is a jpg file with incorrect extension. The LCL TImage (which uses the fcl-image readers/writers) loads the image without any issues. After I renamed the panda.png to a new filename with .jpg extension, only then my program can interpret the image correctly... I guess that when you follow PascalDragon's idea with the ImageHandlers (i.e. loading the image withoug specifying a reader) fcl-image will open the file successfully even when it has a wrong extension. Yes, I do follow @PascalDragon's idea. The following code would not load "panda.png" successfully. (only if it was renamed as "pandapng.jpg" ,etc) I do not know what's wrong. Code: Pascal  [Select][+][-]window.onload = function(){var x1 = document.getElementById("main_content_section"); if (x1) { var x = document.getElementsByClassName("geshi");for (var i = 0; i {$mode objfpc}{$h+}uses fpreadgif, fpreadpng, fpreadbmp, fpreadjpeg, fpimage, classes, sysutils; var img : TFPMemoryImage;{    Reader : TFPCustomImageReader;}    ReadFile : string; procedure Test;var str : TStream; begin  ReadFile := 'c:\fpc\panda.png';   img := TFPMemoryImage.Create(0,0);  img.LoadFromFile (ReadFile);  if FileExists (ReadFile) then  begin    str := TFileStream.Create (ReadFile,fmOpenRead);    try       img.LoadFromStream (str);    finally      str.Free;    end;  end  else    WriteLn (ReadFile,' file not found.');end; procedure Dispose;begin  {Reader.Free;}  img.Free;end; begin  try    Test;    Dispose;  except    on e : exception do      WriteLn ('Error: ',e.message);  end;end. ---------------------------------------------------------------------- wp · 2020-10-05 15:59 I see. This is because the TFPCustomImage calls a class function FindReaderFromFileName which does rely only on the extension. But internally all readers provide a function CheckContents which reads the file header; only when that is correct the reader is used. I wrote the following function for you. It iterates through all registered readers (in ImageHandlers) and calls for each one this CheckContents function: Code: Pascal  [Select][+][-]window.onload = function(){var x1 = document.getElementById("main_content_section"); if (x1) { var x = document.getElementsByClassName("geshi");for (var i = 0; i function GetImageReaderClass(AFileName: String): TFPCustomImageReaderClass;var  stream: TFileStream;  i: Integer;  typeName: String;  readerClass: TFPCustomImageReaderClass;  reader: TFPCustomImageReader;begin  stream := TFileStream.Create(AFileName, fmOpenRead + fmShareDenyNone);  try  for i := 0 to ImageHandlers.Count - 1 do    begin      typeName := ImageHandlers.TypeNames[i];      readerClass := ImageHandlers.ImageReader[typeName];      if Assigned(readerClass) then      begin        reader := readerClass.Create;        try        if reader.CheckContents(stream) then          begin            Result := readerClass;            exit;          end;          stream.Position := 0;        finally          reader.Free;        end;      end;    end;    Result := nil;  finally    stream.Free;  end;end; And here is a small example how this can be applied (I only display the image size for simplicity) Code: Pascal  [Select][+][-]window.onload = function(){var x1 = document.getElementById("main_content_section"); if (x1) { var x = document.getElementsByClassName("geshi");for (var i = 0; i procedure TForm1.Button2Click(Sender: TObject);var  rc: TFPCustomImageReaderClass;  r: TFPCustomImageReader;  img: TFPMemoryImage;begin  Label1.Caption := '';   rc := GetImageReaderClass(fn);  if Assigned(rc) then  begin    img := TFPMemoryImage.Create(1, 1);    try      r := rc.Create;      try      img.LoadFromFile(fn, r);      finally        r.Free;      end;      Label1.Caption := Format('%d x %d', [img.Width, img.Height]);    finally      img.Free;    end;  end  else    ShowMessage('Unknwon image type.');end; ---------------------------------------------------------------------- wp · 2020-10-05 16:14 Ah, and it's even simpler than that, because there is another class function FindHandlerFromStream which does practically the same as my GetImageReaderClass function. My sample program now can be rewritten as: Code: Pascal  [Select][+][-]window.onload = function(){var x1 = document.getElementById("main_content_section"); if (x1) { var x = document.getElementsByClassName("geshi");for (var i = 0; i procedure TForm1.Button2Click(Sender: TObject);var  stream: TFileStream;  readerClass: TFPCustomImageReaderClass;  reader: TFPCustomImageReader;  img: TFPMemoryImage;begin  Label1.Caption := '';   stream := TFileStream.Create(FILE_NAME, fmOpenRead + fmShareDenyNone);  try    readerClass := TFPCustomImage.FindHandlerFromStream(stream).Reader;    if Assigned(readerClass) then begin      img := TFPMemoryImage.Create(0, 0);      try        reader := readerClass.Create;        try          img.LoadFromStream(stream, reader);        finally          reader.Free;        end;        Label1.Caption := Format('%d x %d', [img.Width, img.Height]);      finally        img.Free;      end;    end else      ShowMessage('Unknown image type');  finally    stream.Free;  end;end; Here is a working version of your program: Code: Pascal  [Select][+][-]window.onload = function(){var x1 = document.getElementById("main_content_section"); if (x1) { var x = document.getElementsByClassName("geshi");for (var i = 0; i {$mode objfpc}{$h+}uses fpreadgif, fpreadpng, fpreadbmp, fpreadjpeg, fpimage, classes, sysutils, Crt; var img : TFPMemoryImage;    ReadFile : string;    pixel : TFPColor;    x, y:integer;    ratioX, ratioY:integer; function SimplifyColorComponent(Value:byte):byte;begin if Value>=52 then   SimplifyColorComponent:=63 else if Value>=32 then   SimplifyColorComponent:=42 else if Value>=12 then   SimplifyColorComponent:=21 else   SimplifyColorComponent:=0;end; function DecreaseColor256(Red,Green,Blue:byte):byte;const Palette16:array [0..15,1..3] of byte=((0,0,0),(0,0,42),(0,42,0),(0,42,42),                                     (42,0,0),(42,0,42),(42,42,0),(42,42,42),                                     (0,0,21),(0,0,63),(0,42,21),(0,42,63),                                     (42,0,21),(42,0,63),(42,42,21),(42,42,63));var Color,Component, Value:byte; NewRed,NewGreen,NewBlue:byte; begin DecreaseColor256:=0; C omponent:=1;  repeat   case Component of     1:Value:=SimplifyColorComponent(Red div 4);     2:Value:=SimplifyColorComponent(Green div 4);     3:Value:=SimplifyColorComponent(Blue div 4);   end;    Color:=0;    while Value<>Palette16[Color,Component] do   begin     Inc(Color);    if Color>15 then     begin       Dec(Value,21);       Color:=0;     end;   end;    case Component of     1:NewRed:=Value;     2:NewGreen:=Value;     3:NewBlue:=Value;   end;    Inc(Component);  until Component>3;  for Color:=0 to 15 do   if (Palette16[Color,1]=NewRed) and (Palette16[Color,2]=NewGreen)   and (Palette16[Color,3]=NewBlue) then   begin     DecreaseColor256:=Color;     Exit;   end;end; procedure Init;begin  ReadFile := ParamStr(1);  img := TFPMemoryImage.Create(0,0);end; procedure ReadImage;var  color16, r, g, b:byte;  readerClass: TFPCustomImageReaderClass;  reader: TFPCustomImageReader;  stream: TStream;begin   if FileExists(ReadFile) then   begin     stream := TFileStream.Create(ReadFile, fmOpenRead + fmShareDenyNone);     try       stream.Position := 0;       readerClass := TFPCustomImage.FindHandlerFromStream(stream).Reader;     if Assigned(readerClass) then begin         reader := readerClass.Create;         try           img.LoadFromStream(stream, reader);         finally           reader.Free;         end;          ratioY := img.Height div 30;         ratioX := img.Width div 90;        y:=0;         x:=0;          while (y');  end  else    try      Init;    ReadImage;      Dipose;    except      on e : exception do        writeln ('Error: ',e.message);    end;   ReadLn;end. Please note that your original code tried to read the file twice, by ReadfromFile and then by ReadFromStream. ---------------------------------------------------------------------- anyone · 2020-10-05 17:05 Quote from: wp on October 05, 2020, 06:14:39 pmAh, and it's even simpler than that, because there is another class function FindHandlerFromStream which does practically the same as my GetImageReaderClass function. My sample program now can be rewritten as: Code: Pascal  [Select][+][-]window.onload = function(){var x1 = document.getElementById("main_content_section"); if (x1) { var x = document.getElementsByClassName("geshi");for (var i = 0; i procedure TForm1.Button2Click(Sender: TObject);var  stream: TFileStream;  readerClass: TFPCustomImageReaderClass;  reader: TFPCustomImageReader;  img: TFPMemoryImage;begin  Label1.Caption := '';   stream := TFileStream.Create(FILE_NAME, fmOpenRead + fmShareDenyNone);  try    readerClass := TFPCustomImage.FindHandlerFromStream(stream).Reader;    if Assigned(readerClass) then begin      img := TFPMemoryImage.Create(0, 0);      try        reader := readerClass.Create;        try          img.LoadFromStream(stream, reader);        finally          reader.Free;        end;        Label1.Caption := Format('%d x %d', [img.Width, img.Height]);      finally        img.Free;      end;    end else      ShowMessage('Unknown image type');  finally    stream.Free;  end;end; Here is a working version of your program: (snipped) Please note that your original code tried to read the file twice, by ReadfromFile and then by ReadFromStream. Oh, it is so sweet of you. Thank you. I have updated my original post accordingly (with your modified code). Yes, I have tested with the "panda.png" (JPEG with ".png" file extension) and it works very well. I wish to know your handle or email address (if not convenient, then that's okay) so that I can include it in my source code ("img2chr, developed by [me] and [@wp]") because "wp" is too vague for me to attribute someone. ---------------------------------------------------------------------- circular · 2020-10-05 17:08 Quote from: anyone on October 05, 2020, 04:49:19 pmQuote from: circular on October 05, 2020, 04:35:13 pmIt is possible to save a file in 256 colors, which makes color quantization. You can as well use the palette toolbar. For example generate 16 colors and then apply dithering with it. The image can still have all 24-bit colors but you can get the effect if you like. Is there any programming API for these? It would be nice to use one instead of inventing my own. I did not know that it can be done that way in LazPaint. Yes, all of it is in the unit BGRAColorQuantization. There is a class TBGRAColorQuantizer that handles everything for you. ---------------------------------------------------------------------- lucamar · 2020-10-05 17:17 Quote from: anyone on October 05, 2020, 07:05:55 pm[...] because "wp" is too vague for me to attribute someone. Says "anyone" ---------------------------------------------------------------------- anyone · 2020-10-05 17:31 Quote from: circular on October 05, 2020, 07:08:54 pmQuote from: anyone on October 05, 2020, 04:49:19 pmIs there any programming API for these? It would be nice to use one instead of inventing my own. I did not know that it can be done that way in LazPaint. Yes, all of it is in the unit BGRAColorQuantization. There is a class TBGRAColorQuantizer that handles everything for you. Noted with thanks. ---------------------------------------------------------------------- anyone · 2020-10-05 17:34 Quote from: lucamar on October 05, 2020, 07:17:13 pmQuote from: anyone on October 05, 2020, 07:05:55 pm[...] because "wp" is too vague for me to attribute someone. Says "anyone" Haha.    Actually I wanted to use "nobody" but someone has already taken that name. ---------------------------------------------------------------------- wp · 2020-10-05 17:42 Quote from: anyone on October 05, 2020, 07:05:55 pmI wish to know your handle or email address (if not convenient, then that's okay) so that I can include it in my source code ("img2chr, developed by [me] and [@wp]") because "wp" is too vague for me to attribute someone. There is no reason to attribute me for this little piece of code. If you want you can acknowledge "contributions from the Lazarus forum" (mine was not the only one). ---------------------------------------------------------------------- wp · 2020-10-06 15:03 Quote from: wp on October 05, 2020, 06:14:39 pmCode: Pascal  [Select][+][-]window.onload = function(){var x1 = document.getElementById("main_content_section"); if (x1) { var x = document.getElementsByClassName("geshi");for (var i = 0; i procedure ReadImage;var  color16, r, g, b:byte;  readerClass: TFPCustomImageReaderClass;  reader: TFPCustomImageReader;  stream: TStream;begin   if FileExists(ReadFile) then   begin     stream := TFileStream.Create(ReadFile, fmOpenRead + fmShareDenyNone);     try     stream.Position := 0;       readerClass := TFPCustomImage.FindHandlerFromStream(stream).Reader;       if Assigned(readerClass) then begin         reader := readerClass.Create;         try           img.LoadFromStream(stream, reader);         finally           reader.Free;         end;... Just for completeness: the line "stream.Position := 0" highlighted in my code snippet above is not necessary (the stream position is always zero after creation anyway) and can be omitted; it is a left-over of a previous version of the code. ---------------------------------------------------------------------- anyone · 2020-10-06 15:37 Quote from: wp on October 06, 2020, 05:03:33 pm Just for completeness: the line "stream.Position := 0" highlighted in my code snippet above is not necessary (the stream position is always zero after creation anyway) and can be omitted; it is a left-over of a previous version of the code. Thanks for the update, @wp, I will remove that in my next version of source code.