Lazarus

Programming => Graphics and Multimedia => Graphics => Topic started by: anyone on October 04, 2020, 06:32:52 pm

Title: My hobby project (image-to-character)
Post by: anyone on October 04, 2020, 06:32:52 pm
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][+][-]
  1. {$mode objfpc}{$h+}
  2. uses fpreadgif, fpreadpng, fpreadbmp, fpreadjpeg, fpimage, classes, sysutils, Crt;
  3.  
  4. var img : TFPMemoryImage;
  5.     ReadFile : string;
  6.     pixel : TFPColor;
  7.     x, y:integer;
  8.     ratioX, ratioY:integer;
  9.  
  10. function SimplifyColorComponent(Value:byte):byte;
  11. begin
  12.  if Value>=52 then
  13.    SimplifyColorComponent:=63
  14.  else
  15.  if Value>=32 then
  16.    SimplifyColorComponent:=42
  17.  else
  18.  if Value>=12 then
  19.    SimplifyColorComponent:=21
  20.  else
  21.    SimplifyColorComponent:=0;
  22. end;
  23.  
  24. function DecreaseColor256(Red,Green,Blue:byte):byte;
  25. const
  26.  Palette16:array [0..15,1..3] of byte=((0,0,0),(0,0,42),(0,42,0),(0,42,42),
  27.                                        (42,0,0),(42,0,42),(42,42,0),(42,42,42),
  28.                                        (0,0,21),(0,0,63),(0,42,21),(0,42,63),
  29.                                        (42,0,21),(42,0,63),(42,42,21),(42,42,63));
  30. var
  31.  Color,Component,Value:byte;
  32.  NewRed,NewGreen,NewBlue:byte;
  33.  
  34. begin
  35.  DecreaseColor256:=0;
  36.  Component:=1;
  37.  
  38.  repeat
  39.    case Component of
  40.      1:Value:=SimplifyColorComponent(Red div 4);
  41.      2:Value:=SimplifyColorComponent(Green div 4);
  42.      3:Value:=SimplifyColorComponent(Blue div 4);
  43.    end;
  44.  
  45.    Color:=0;
  46.  
  47.    while Value<>Palette16[Color,Component] do
  48.    begin
  49.      Inc(Color);
  50.  
  51.      if Color>15 then
  52.      begin
  53.        Dec(Value,21);
  54.        Color:=0;
  55.      end;
  56.    end;
  57.  
  58.    case Component of
  59.      1:NewRed:=Value;
  60.      2:NewGreen:=Value;
  61.      3:NewBlue:=Value;
  62.    end;
  63.  
  64.    Inc(Component);
  65.  
  66.  until Component>3;
  67.  
  68.  for Color:=0 to 15 do
  69.    if (Palette16[Color,1]=NewRed) and (Palette16[Color,2]=NewGreen)
  70.    and (Palette16[Color,3]=NewBlue) then
  71.    begin
  72.      DecreaseColor256:=Color;
  73.      Exit;
  74.    end;
  75. end;
  76.  
  77. procedure Init;
  78. begin
  79.   ReadFile := ParamStr(1);
  80.   img := TFPMemoryImage.Create(0,0);
  81. end;
  82.  
  83. procedure ReadImage;
  84. var
  85.   color16, r, g, b:byte;
  86.   readerClass: TFPCustomImageReaderClass;
  87.   reader: TFPCustomImageReader;
  88.   stream: TStream;
  89. begin
  90.    if FileExists(ReadFile) then
  91.    begin
  92.      stream := TFileStream.Create(ReadFile, fmOpenRead + fmShareDenyNone);
  93.      try
  94.        stream.Position := 0;
  95.        readerClass := TFPCustomImage.FindHandlerFromStream(stream).Reader;
  96.        if Assigned(readerClass) then begin
  97.          reader := readerClass.Create;
  98.          try
  99.            img.LoadFromStream(stream, reader);
  100.          finally
  101.            reader.Free;
  102.          end;
  103.  
  104.          ratioY := img.Height div 30;
  105.          ratioX := img.Width div 90;
  106.  
  107.          y:=0;
  108.          x:=0;
  109.  
  110.          while (y<img.Height) do
  111.          begin
  112.            while (x<img.Width) do
  113.            begin
  114.              pixel := img.Colors[x,y];
  115.              r := (pixel.Red shr 8) and $00ff;
  116.              g := (pixel.Green shr 8) and $00ff;
  117.              b := (pixel.Blue shr 8) and $00ff;
  118.              color16:=DecreaseColor256(r, g, b);
  119.  
  120.              TextBackground(color16);
  121.              Write(' ');
  122.  
  123.              x:=x+ratioX;
  124.            end;
  125.  
  126.            WriteLn;
  127.            x:=0;
  128.            y:=y+ratioY;
  129.          end;
  130.        end else
  131.          WriteLn('Unknown image format.');
  132.      finally
  133.        stream.Free;
  134.      end;
  135.    end
  136.    else
  137.       WriteLn (ReadFile,' file not found.');
  138. end;
  139.  
  140. procedure Dispose;
  141. begin
  142.   Img.Free;
  143. end;
  144.  
  145. begin
  146.   if (ParamCount=0) then
  147.   begin
  148.     WriteLn('img2chr ,developed by bookhanming@outlook.my');
  149.     WriteLn;
  150.     WriteLn('img2chr <image filename>');
  151.   end
  152.   else
  153.     try
  154.       Init;
  155.       ReadImage;
  156.       Dispose;
  157.     except
  158.       on e : exception do
  159.         writeln ('Error: ',e.message);
  160.     end;
  161.  
  162.   ReadLn;
  163. end.

Example screen shot is attached together. (Left: output of this img2chr program using .... Right: original Panda.jpg photo)
Title: Re: My hobby project (image-to-character)
Post by: MarkMLl on October 04, 2020, 09:59:36 pm
1) 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


Title: Re: My hobby project (image-to-character)
Post by: Handoko on October 05, 2020, 04:36:59 am
1) 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][+][-]
  1. var
  2.   Reader : TFPCustomImageReader;
  3.   FileName: string;
  4.   Sig: array of char;
  5.   S: string;
  6. begin
  7.  
  8.   if ParamCount <= 0 then
  9.   begin
  10.     WriteLn('Usage: img2chr <image filename>');
  11.     Halt;
  12.   end;
  13.  
  14.   Filename := ParamStr(1);
  15.   Reader := nil;
  16.  
  17.   // Open based on file extension
  18.   S :=  ExtractFileExt(Filename.ToLower);
  19.   if S = '.jpg' then TryOpenAsJPG(Reader, Filename)
  20.   else
  21.     if S = '.png' then TryOpenAsPNG(Reader, Filename)
  22.     else
  23.       if S = '.bmp' then TryOpenAsBMP(Reader, Filename)
  24.       else
  25.         if S = '.gif' then TryOpenAsGIF(Reader, Filename));
  26.  
  27.   // Open based on magic number
  28.   if not(Assigned(Reader) then
  29.   begin
  30.     Sig := ReadFileSignature(Filename);
  31.     If SignatureIsJPG then TryOpenAsJPG(Reader, Filename)
  32.     else
  33.       if SignatureIsPNG then TryOpenAsPNG(Reader, Filename)
  34.       else
  35.         if SignatureIsBMP then TryOpenAsBMP(Reader, Filename)
  36.         else
  37.           if SignatureIsGIF then TryOpenAsGIF(Reader, Filename)
  38.   end;
  39.  
  40.   if not(Assgined(Reader)) then
  41.   begin
  42.     WriteLn('Unrecognizable file format.');
  43.     Halt;
  44.   end;
  45.  
  46. end;

You will need write all the TryOpenAs* and SignatureIs* and ReadFileSignature functions.
Title: Re: My hobby project (image-to-character)
Post by: PascalDragon on October 05, 2020, 09:17:08 am
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.
Title: Re: My hobby project (image-to-character)
Post by: Fred vS on October 05, 2020, 10:42:32 am
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][+][-]
  1. function DetectFileFormat(AFilenameUTF8: string): TBGRAImageFormat

  {** Detect the file format of a given stream. ''ASuggestedExtensionUTF8'' can be provided to guess the format }
 
Code: Pascal  [Select][+][-]
  1.  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][+][-]
  1.   function SuggestImageFormat(AFilenameOrExtensionUTF8: string): TBGRAImageFormat;

  {** Returns a likely image extension for the format }
 
Code: Pascal  [Select][+][-]
  1. function SuggestImageExtension(AFormat: TBGRAImageFormat): string;

  {** Create an image reader for the given format }
Code: Pascal  [Select][+][-]
  1.   function CreateBGRAImageReader(AFormat: TBGRAImageFormat): FPCustomImageReader;

  {** Create an image writer for the given format. ''AHasTransparentPixels'' specifies if alpha channel must be supported }
 
Code: Pascal  [Select][+][-]
  1.  function CreateBGRAImageWriter(AFormat: TBGRAImageFormat; AHasTransparentPixels: boolean): TFPCustomImageWriter;

Fre;D

PS: Thanks to Circular.
Title: Re: My hobby project (image-to-character)
Post by: MarkMLl on October 05, 2020, 01:07:16 pm
Thanks for updating us Fred :-)

MarkMLl
Title: Re: My hobby project (image-to-character)
Post by: anyone on October 05, 2020, 01:28:17 pm
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?


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.

Looks like TFPMemoryImage.LoadFromFile would auto-detect file format?

And yes, thank you, it works.
Title: Re: My hobby project (image-to-character)
Post by: circular on October 05, 2020, 02:57:32 pm
However, 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.

Quote
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?
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.
Title: Re: My hobby project (image-to-character)
Post by: MarkMLl on October 05, 2020, 03:30:07 pm
However, 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
Title: Re: My hobby project (image-to-character)
Post by: anyone on October 05, 2020, 03:45:03 pm
However, 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)

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.

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.
Title: Re: My hobby project (image-to-character)
Post by: anyone on October 05, 2020, 04:05:18 pm
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][+][-]
  1. C:\FPC>img2chr panda.png
  2. Error: Error while reading stream: Wrong image format
Title: Re: My hobby project (image-to-character)
Post by: Fred vS on October 05, 2020, 04:14:42 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.
Title: Re: My hobby project (image-to-character)
Post by: wp on October 05, 2020, 04:17:16 pm
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][+][-]
  1. C:\FPC>img2chr panda.png
  2. Error: Error while reading stream: Wrong image format
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.
Title: Re: My hobby project (image-to-character)
Post by: Fred vS on October 05, 2020, 04:24:58 pm
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][+][-]
  1. C:\FPC>img2chr panda.png
  2. Error: Error while reading stream: Wrong image format
Trying 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):

Title: Re: My hobby project (image-to-character)
Post by: circular on October 05, 2020, 04:35:13 pm
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.
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.
Title: Re: My hobby project (image-to-character)
Post by: anyone on October 05, 2020, 04:43:05 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...
Title: Re: My hobby project (image-to-character)
Post by: anyone on October 05, 2020, 04:45:41 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.
Title: Re: My hobby project (image-to-character)
Post by: anyone on October 05, 2020, 04:49:19 pm
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.
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.
Title: Re: My hobby project (image-to-character)
Post by: wp on October 05, 2020, 04:50:42 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.
Title: Re: My hobby project (image-to-character)
Post by: anyone on October 05, 2020, 04:59:40 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][+][-]
  1. {$mode objfpc}{$h+}
  2. uses fpreadgif, fpreadpng, fpreadbmp, fpreadjpeg, fpimage, classes, sysutils;
  3.  
  4. var img : TFPMemoryImage;
  5. {    Reader : TFPCustomImageReader;}
  6.     ReadFile : string;
  7.  
  8. procedure Test;
  9. var str : TStream;
  10.  
  11. begin
  12.   ReadFile := 'c:\fpc\panda.png';
  13.  
  14.   img := TFPMemoryImage.Create(0,0);
  15.   img.LoadFromFile (ReadFile);
  16.   if FileExists (ReadFile) then
  17.   begin
  18.     str := TFileStream.Create (ReadFile,fmOpenRead);
  19.     try
  20.        img.LoadFromStream (str);
  21.     finally
  22.       str.Free;
  23.     end;
  24.   end
  25.   else
  26.     WriteLn (ReadFile,' file not found.');
  27. end;
  28.  
  29. procedure Dispose;
  30. begin
  31.   {Reader.Free;}
  32.   img.Free;
  33. end;
  34.  
  35. begin
  36.   try
  37.     Test;
  38.     Dispose;
  39.   except
  40.     on e : exception do
  41.       WriteLn ('Error: ',e.message);
  42.   end;
  43. end.
  44.  
Title: Re: My hobby project (image-to-character)
Post by: wp on October 05, 2020, 05:59:34 pm
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][+][-]
  1. function GetImageReaderClass(AFileName: String): TFPCustomImageReaderClass;
  2. var
  3.   stream: TFileStream;
  4.   i: Integer;
  5.   typeName: String;
  6.   readerClass: TFPCustomImageReaderClass;
  7.   reader: TFPCustomImageReader;
  8. begin
  9.   stream := TFileStream.Create(AFileName, fmOpenRead + fmShareDenyNone);
  10.   try
  11.     for i := 0 to ImageHandlers.Count - 1 do
  12.     begin
  13.       typeName := ImageHandlers.TypeNames[i];
  14.       readerClass := ImageHandlers.ImageReader[typeName];
  15.       if Assigned(readerClass) then
  16.       begin
  17.         reader := readerClass.Create;
  18.         try
  19.           if reader.CheckContents(stream) then
  20.           begin
  21.             Result := readerClass;
  22.             exit;
  23.           end;
  24.           stream.Position := 0;
  25.         finally
  26.           reader.Free;
  27.         end;
  28.       end;
  29.     end;
  30.     Result := nil;
  31.   finally
  32.     stream.Free;
  33.   end;
  34. end;

And here is a small example how this can be applied (I only display the image size for simplicity)

Code: Pascal  [Select][+][-]
  1. procedure TForm1.Button2Click(Sender: TObject);
  2. var
  3.   rc: TFPCustomImageReaderClass;
  4.   r: TFPCustomImageReader;
  5.   img: TFPMemoryImage;
  6. begin
  7.   Label1.Caption := '';
  8.  
  9.   rc := GetImageReaderClass(fn);
  10.   if Assigned(rc) then
  11.   begin
  12.     img := TFPMemoryImage.Create(1, 1);
  13.     try
  14.       r := rc.Create;
  15.       try
  16.         img.LoadFromFile(fn, r);
  17.       finally
  18.         r.Free;
  19.       end;
  20.       Label1.Caption := Format('%d x %d', [img.Width, img.Height]);
  21.     finally
  22.       img.Free;
  23.     end;
  24.   end
  25.   else
  26.     ShowMessage('Unknwon image type.');
  27. end;
Title: Re: My hobby project (image-to-character)
Post by: wp on October 05, 2020, 06:14:39 pm
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][+][-]
  1. procedure TForm1.Button2Click(Sender: TObject);
  2. var
  3.   stream: TFileStream;
  4.   readerClass: TFPCustomImageReaderClass;
  5.   reader: TFPCustomImageReader;
  6.   img: TFPMemoryImage;
  7. begin
  8.   Label1.Caption := '';
  9.  
  10.   stream := TFileStream.Create(FILE_NAME, fmOpenRead + fmShareDenyNone);
  11.   try
  12.     readerClass := TFPCustomImage.FindHandlerFromStream(stream).Reader;
  13.     if Assigned(readerClass) then begin
  14.       img := TFPMemoryImage.Create(0, 0);
  15.       try
  16.         reader := readerClass.Create;
  17.         try
  18.           img.LoadFromStream(stream, reader);
  19.         finally
  20.           reader.Free;
  21.         end;
  22.         Label1.Caption := Format('%d x %d', [img.Width, img.Height]);
  23.       finally
  24.         img.Free;
  25.       end;
  26.     end else
  27.       ShowMessage('Unknown image type');
  28.   finally
  29.     stream.Free;
  30.   end;
  31. end;

Here is a working version of your program:

Code: Pascal  [Select][+][-]
  1. {$mode objfpc}{$h+}
  2. uses fpreadgif, fpreadpng, fpreadbmp, fpreadjpeg, fpimage, classes, sysutils, Crt;
  3.  
  4. var img : TFPMemoryImage;
  5.     ReadFile : string;
  6.     pixel : TFPColor;
  7.     x, y:integer;
  8.     ratioX, ratioY:integer;
  9.  
  10. function SimplifyColorComponent(Value:byte):byte;
  11. begin
  12.  if Value>=52 then
  13.    SimplifyColorComponent:=63
  14.  else
  15.  if Value>=32 then
  16.    SimplifyColorComponent:=42
  17.  else
  18.  if Value>=12 then
  19.    SimplifyColorComponent:=21
  20.  else
  21.    SimplifyColorComponent:=0;
  22. end;
  23.  
  24. function DecreaseColor256(Red,Green,Blue:byte):byte;
  25. const
  26.  Palette16:array [0..15,1..3] of byte=((0,0,0),(0,0,42),(0,42,0),(0,42,42),
  27.                                        (42,0,0),(42,0,42),(42,42,0),(42,42,42),
  28.                                        (0,0,21),(0,0,63),(0,42,21),(0,42,63),
  29.                                        (42,0,21),(42,0,63),(42,42,21),(42,42,63));
  30. var
  31.  Color,Component,Value:byte;
  32.  NewRed,NewGreen,NewBlue:byte;
  33.  
  34. begin
  35.  DecreaseColor256:=0;
  36.  Component:=1;
  37.  
  38.  repeat
  39.    case Component of
  40.      1:Value:=SimplifyColorComponent(Red div 4);
  41.      2:Value:=SimplifyColorComponent(Green div 4);
  42.      3:Value:=SimplifyColorComponent(Blue div 4);
  43.    end;
  44.  
  45.    Color:=0;
  46.  
  47.    while Value<>Palette16[Color,Component] do
  48.    begin
  49.      Inc(Color);
  50.  
  51.      if Color>15 then
  52.      begin
  53.        Dec(Value,21);
  54.        Color:=0;
  55.      end;
  56.    end;
  57.  
  58.    case Component of
  59.      1:NewRed:=Value;
  60.      2:NewGreen:=Value;
  61.      3:NewBlue:=Value;
  62.    end;
  63.  
  64.    Inc(Component);
  65.  
  66.  until Component>3;
  67.  
  68.  for Color:=0 to 15 do
  69.    if (Palette16[Color,1]=NewRed) and (Palette16[Color,2]=NewGreen)
  70.    and (Palette16[Color,3]=NewBlue) then
  71.    begin
  72.      DecreaseColor256:=Color;
  73.      Exit;
  74.    end;
  75. end;
  76.  
  77. procedure Init;
  78. begin
  79.   ReadFile := ParamStr(1);
  80.   img := TFPMemoryImage.Create(0,0);
  81. end;
  82.  
  83. procedure ReadImage;
  84. var
  85.   color16, r, g, b:byte;
  86.   readerClass: TFPCustomImageReaderClass;
  87.   reader: TFPCustomImageReader;
  88.   stream: TStream;
  89. begin
  90.    if FileExists(ReadFile) then
  91.    begin
  92.      stream := TFileStream.Create(ReadFile, fmOpenRead + fmShareDenyNone);
  93.      try
  94.        stream.Position := 0;
  95.        readerClass := TFPCustomImage.FindHandlerFromStream(stream).Reader;
  96.        if Assigned(readerClass) then begin
  97.          reader := readerClass.Create;
  98.          try
  99.            img.LoadFromStream(stream, reader);
  100.          finally
  101.            reader.Free;
  102.          end;
  103.  
  104.          ratioY := img.Height div 30;
  105.          ratioX := img.Width div 90;
  106.  
  107.          y:=0;
  108.          x:=0;
  109.  
  110.          while (y<img.Height) do
  111.          begin
  112.            while (x<img.Width) do
  113.            begin
  114.              pixel := img.Colors[x,y];
  115.              r := (pixel.Red shr 8) and $00ff;
  116.              g := (pixel.Green shr 8) and $00ff;
  117.              b := (pixel.Blue shr 8) and $00ff;
  118.              color16:=DecreaseColor256(r, g, b);
  119.  
  120.              TextBackground(color16);
  121.              Write(' ');
  122.  
  123.              x:=x+ratioX;
  124.            end;
  125.  
  126.            WriteLn;
  127.            x:=0;
  128.            y:=y+ratioY;
  129.          end;
  130.        end else
  131.          WriteLn('Unknown image format.');
  132.      finally
  133.        stream.Free;
  134.      end;
  135.    end
  136.    else
  137.       WriteLn (ReadFile,' file not found.');
  138. end;
  139.  
  140. procedure Dipose;
  141. begin
  142.   Img.Free;
  143. end;
  144.  
  145. begin
  146.   if (ParamCount=0) then
  147.   begin
  148.     WriteLn('img2chr ,developed by bookhanming@outlook.my');
  149.     WriteLn;
  150.     WriteLn('img2chr <image filename>');
  151.   end
  152.   else
  153.     try
  154.       Init;
  155.       ReadImage;
  156.       Dipose;
  157.     except
  158.       on e : exception do
  159.         writeln ('Error: ',e.message);
  160.     end;
  161.  
  162.   ReadLn;
  163. end.

Please note that your original code tried to read the file twice, by ReadfromFile and then by ReadFromStream.
Title: Re: My hobby project (image-to-character)
Post by: anyone on October 05, 2020, 07:05:55 pm
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][+][-]
  1. procedure TForm1.Button2Click(Sender: TObject);
  2. var
  3.   stream: TFileStream;
  4.   readerClass: TFPCustomImageReaderClass;
  5.   reader: TFPCustomImageReader;
  6.   img: TFPMemoryImage;
  7. begin
  8.   Label1.Caption := '';
  9.  
  10.   stream := TFileStream.Create(FILE_NAME, fmOpenRead + fmShareDenyNone);
  11.   try
  12.     readerClass := TFPCustomImage.FindHandlerFromStream(stream).Reader;
  13.     if Assigned(readerClass) then begin
  14.       img := TFPMemoryImage.Create(0, 0);
  15.       try
  16.         reader := readerClass.Create;
  17.         try
  18.           img.LoadFromStream(stream, reader);
  19.         finally
  20.           reader.Free;
  21.         end;
  22.         Label1.Caption := Format('%d x %d', [img.Width, img.Height]);
  23.       finally
  24.         img.Free;
  25.       end;
  26.     end else
  27.       ShowMessage('Unknown image type');
  28.   finally
  29.     stream.Free;
  30.   end;
  31. 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.
 :)
Title: Re: My hobby project (image-to-character)
Post by: circular on October 05, 2020, 07:08:54 pm
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.
Yes, all of it is in the unit BGRAColorQuantization. There is a class TBGRAColorQuantizer that handles everything for you.
Title: Re: My hobby project (image-to-character)
Post by: lucamar on October 05, 2020, 07:17:13 pm
[...] because "wp" is too vague for me to attribute someone.
 :)

Says "anyone" :D
Title: Re: My hobby project (image-to-character)
Post by: anyone on October 05, 2020, 07:31:12 pm
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.

Noted with thanks.  :)
Title: Re: My hobby project (image-to-character)
Post by: anyone on October 05, 2020, 07:34:30 pm
[...] because "wp" is too vague for me to attribute someone.
 :)

Says "anyone" :D

Haha.  :D  Actually I wanted to use "nobody" but someone has already taken that name.
Title: Re: My hobby project (image-to-character)
Post by: wp on October 05, 2020, 07:42:00 pm
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.
 :)
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).
Title: Re: My hobby project (image-to-character)
Post by: wp on October 06, 2020, 05:03:33 pm
Code: Pascal  [Select][+][-]
  1. procedure ReadImage;
  2. var
  3.   color16, r, g, b:byte;
  4.   readerClass: TFPCustomImageReaderClass;
  5.   reader: TFPCustomImageReader;
  6.   stream: TStream;
  7. begin
  8.    if FileExists(ReadFile) then
  9.    begin
  10.      stream := TFileStream.Create(ReadFile, fmOpenRead + fmShareDenyNone);
  11.      try
  12.        stream.Position := 0;
  13.        readerClass := TFPCustomImage.FindHandlerFromStream(stream).Reader;
  14.        if Assigned(readerClass) then begin
  15.          reader := readerClass.Create;
  16.          try
  17.            img.LoadFromStream(stream, reader);
  18.          finally
  19.            reader.Free;
  20.          end;
  21. ...

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.
Title: Re: My hobby project (image-to-character)
Post by: anyone on October 06, 2020, 05:37:23 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.

 8)
TinyPortal © 2005-2018