Sprites processing with standard Lazarus tools lazarus / 39-Graphics ---------------------------------------------------------------------- _N_ · 2021-09-20 11:42 Hello all. I am writing piece of software that works with sprites. It should be able to use external resources that are stored in png images (I cannot embed them). Also, I don't need all of them, but only some subset (subset of sprites in every png). So for now my plan is the following: 1. Ask use for the path to original sprites 2. Pick needed sprites, process them, and save near the application 3. Load processed sprites at runtime (no modifications at this step) But I have problem with step 2 (I tried to do the processing from original resources at runtime, but it was very slow (I also do repainting), so decided to spend a few megabytes of RAM to speed up everything and do processing beforehand and only load result at runtime). What's needed for a sprite file: 1. Get several first sprites from png image (I know sizes for each file). 2. Duplicate them with flips to get "full row". 3. Copy the row several times, but repainting some colors (I have color mapping what should be replaced). 4. Save all this into a png file that will be used further on. I don't want to use OpenGL or similar and also would like not to depend on an external library (but still consider it as an option if using standard mechanism is problematic). For now I have: 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   PNG := TPortableNetworkGraphic.Create();  Sprites := TImageList.Create(nil);  try    PNG.LoadFromFile(SourceFile);     Sprites.Width := PNG.Width div 5;    Sprites.Height := getHeightByName(SpriteName);    // Sprites.BkColor := clBlack;    // Sprites.DrawingStyle := dsTransparent;     for p := 1 to 10 do begin      Sprites.AddSliced(PNG, 5, 1); // Quick way to extract needed sprites from png       // HERE I NEED TO ADD FLIPPED/ROTATED EXISTING SPRITES       // Repaint sprites      for i := 0 to ROW_SPRITES_COUNT - 1 do begin        SpriteIndex := p * ROW_SPRITES_COUNT + i;         SpriteBitmap := TBitmap.Create();        try          Sprites.GetBitmap(SpriteIndex, SpriteBitmap);          RepaintSprite(SpriteBitmap, ORIGINAL_COLOR, DESIRED_COLOR);          Sprites.Replace(SpriteIndex, SpriteBitmap, nil);        finally        SpriteBitmap.Free();        end;      end;    end;     // Save image list to png file      Bitmap := TBitmap.Create();    Bitmap.Height := Sprites.Height;    Bitmap.Width := Sprites.Width * Sprites.Count;    for i:=0 to (Sprites.Count - 1) do      Sprites.Draw(Bitmap.Canvas, i * Sprites.Width, 0 , i);    try      // THIS LOSE TRANSPARENCY WHICH IS NEEDED      // I have to use png imgae instead bitmap here but I can get only bitmap from image list, what to do?      Bitmap.SaveToFile(DestFile);    finally      Bitmap.Free();    end;   finally    Sprites.Free();    PNG.Free();  end;       // -------------------------------------------- procedure RepaintSprite  if (ColorFrom = ColorTo) then Exit;  Bitmap.BeginUpdate();  for y:=0 to (Bitmap.Height - 1) do begin    for x:=0 to (Bitmap.Width - 1) do begin      // I know that it is possible to read whole row,  but I read somewhere      // that it may bring trouble on different platforms, is it true?      PixelColor := Bitmap.Canvas.Pixels[x, y];      for i:=0 to SHADES_NUMBER do begin        if (PixelColor = SPRITES_COLORS[ColorFrom][i]) then begin          Bitmap.Canvas.Pixels[x, y] := SPRITES_COLORS[ColorTo][i];          break;        end;      end;    end;  end;  Bitmap.EndUpdate();   To sum up: 1. How to copy & flip sprites and and them into the same imagelist (or maybe I do not need image list at all here, but then I have to read sprites from original png myself, but I don't know how to copy an area and keep transparency...)? 2. Repaint. First, how to correctly replace (or maybe add after repaint) in imagelist. Second, but not critical, optimization. Pixel access is very slow, but can I safely use faster method (and keep cross platform support)? At least I can repaint first, so after flip I don't need to repaint again. 3. How to save back to png file. How to get sprite from imagelist keeping transparency and draw on resulting png in memory? I do not need compatibility with Delphi, so using Lazarus specific classes are completely ok, but I need to support cross platform compilation. I don't have much experience with images processing in Lazarus or Pascal, so sorry for several, probably silly questions (for people who know)... I googled, but there are different problems with different solutions... And people use different approaches: TBitmap, TImage, TPicture, TCanvas, Masks, etc (actually it should be sort of the same, but again if you know details about implementations). I got somewhat lost. Code above is what I was able to achieve for now... Thank you in advance. ---------------------------------------------------------------------- Mr.Madguy · 2021-09-22 07:17 Unfortunately it sounds like "I need advanced 2D processing tools, but I don't want to use them". Some things, you ask for, like copying parts of bitmaps and even flipping them (not 100% sure about TCanvas, but raw GDI allows it), are possible. But other things, like color mapping, aren't possible. At least if you don't use 8bit palette, but 16/32bit target without palette. FPC doesn't seem to support 8bit bitmaps though. You can always try memory bitmaps, like DIBs from GDI. But if you want cross-platform solution, then may be you should try BGRABitmap. Overall advanced 2D processing requires something like DirectDraw. But once DirectDraw is obsoleted and 2D is now considered to be subset of 3D - 3D libraries should be used instead. ---------------------------------------------------------------------- marcov · 2021-09-22 08:49 Any  Bitmap.Canvas.Pixels[x, y] is slow. Try to find or create a dedicated copy procedure that optimizes this. ---------------------------------------------------------------------- Mr.Madguy · 2021-09-22 09:47 Pure Canvas can actually be used. Sprite flipping can be achieved via code 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   Canvas.CopyRect(ClientRect, Picture.Bitmap.Canvas, TRect.Create(0, Picture.Height, Picture.Width, 0));  Repainting some colors is harder task, but can actually be achieved via using masked bitmap. I won't provide full working example, but here is template: 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 //This two are done every time, you need to change repaint colorBackdropBitmap.Canvas.Brush.Color := MyColor;BackdropBitmap.Canvas.FillRect(TRect.Create(0, 0, BackdropBitmap.Width, BackdropBitmap.Height));//Not sure, if 1x1 pixel bitmap can be used here for optimization purposes//Load predefined MaskBitmap hereBackdropBitmap.MaskHandle := MaskBitmap.ReleaseHandle;//You need to do it only once//You may destroy MaskBitmap after thatCanvas.Draw(0, 0, MySprite);Canvas.Draw(0, 0, BackdropBitmap);//Just another way to get mask bitmap, if one color should be changed to anotherMySprite.Mask(ColorToChange);//Ehhh. Mask is reversed here - you need to manually reverse it.BackdropBitmap.MaskHandle := MySprite.ReleaseMaskHandle; ---------------------------------------------------------------------- wp · 2021-09-22 09:53 There is a demo in folder examples/sprites of the Lazarus installation. ---------------------------------------------------------------------- Mr.Madguy · 2021-09-22 11:16 Here is example. Every time you click image - new random color and direction are generated. Sorry for a little bit messy code. No time to optimize it. ---------------------------------------------------------------------- _N_ · 2021-09-24 05:49 Thank you, all, for the help. I am still trying to solve the issue. Mr.Madguy, thank you for the sample project. I got some ideas from it, but unfortunately it doesn't work under Linux (I see Mario on blue background or just a random color that fills whole image). CopyRect does the work with flips, thanks! wp, you are right, I forgot to look at samples when posting this, sorry, but now I know that the samples don't help with my issue. marcov, thanks, I understand that it migth work, but it requires knowledge about some internals (that I don't have) and also a subject to get issues between platforms... So, the code I have now (it doesn't do completely what I need and very slow because of repaint, but maybe someone find it, or parts of it, useful): 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   SpritesImage := TPortableNetworkGraphic.Create();  ResultImage := TPortableNetworkGraphic.Create();  //ResultImage.PixelFormat := pf32bit;  try    SpritesImage.LoadFromFile(SourceFile);     SpriteWidth := SpritesImage.Width div UNITS_SPRITES_PER_ROW;    SpriteHeight := SpriteData.SpriteHeight;    ResultImage.Width := SpriteWidth * SPRITES_PER_PLAYER_UNIT;    ResultImage.Height := SpriteHeight * MAX_PLAYER_NUMBER;     // For copying and flipping CopyRect is used, but it loses transparency! Not resolved yet, but if one doesn't need transparency works great!    // Copy original 5 sprites    SrcRect := TRect.Create(0, 0, SpritesImage.Width, SpriteHeight);    DestRect := TRect.Create(0, 0, SpritesImage.Width, SpriteHeight);    ResultImage.Canvas.CopyRect(DestRect, SpritesImage.Canvas, SrcRect);    // Add missing sprites to the sequence    // It should: 1 2 3 4 5 -> 1 2 3 4 5 4' 3' 2'    for i := 1 to 3 do begin      SrcIndex := UNITS_SPRITES_PER_ROW - i;      DestIndex := UNITS_SPRITES_PER_ROW + i;      // Reverse destination x axis to get horizontal flip effect      SrcRect := TRect.Create((SrcIndex - 1) * SpriteWidth, 0, SrcIndex * SpriteWidth, SpriteHeight);      DestRect := TRect.Create(DestIndex * SpriteWidth, 0, (DestIndex - 1) * SpriteWidth, SpriteHeight);      ResultImage.Canvas.CopyRect(DestRect, SpritesImage.Canvas, SrcRect);  end;     // Duplicate first row    SrcRect := TRect.Create(0, 0, ResultImage.Width, SpriteHeight);    for c := 2 to 8 do begin      DestRect := TRect.Create(0, c * SpriteHeight, ResultImage.Width, (c + 1) * SpriteHeight);      ResultImage.Canvas.CopyRect(DestRect, ResultImage.Canvas, SrcRect);    end;     // Approach with Canvas.Pixels is extremely slow (~10 sec per image which is ~700x700 px on old 2HGz Celeron), but safe unlike ScanLine    // Also I don't use Begin/EndUpdate as using them causes just a black color over all image (even calling EndUpdate right after BeginUpdate! Have no idea why).    // Repaint each row for corresponding color    // First row already has correct color    for c := 2 to 8 do begin      for y := c * SpriteHeight to (c + 1) * SpriteHeight do begin        for x := 0 to ResultImage.Width do begin          PixelColor := ResultImage.Canvas.Pixels[x, y];          for i := 0 to COLOR_SHADES_NUMBER do begin            if (PixelColor = SPRITES_COLORS[1][i]) then begin              ResultImage.Canvas.Pixels[x, y] := SPRITES_COLORS[c][i];              break;            end;          end;        end;      end;    end;   //ResultImage.TransparentColor := clBlack;    ResultImage.SaveToFile(ResultFileName + '.png']));  finally    SpritesImage.Free();    ResultImage.Free();  end;                                   Looking at BGRABitmap now... seems like a real option to go with. ---------------------------------------------------------------------- Mr.Madguy · 2021-09-25 04:34 Unfortunately I won't be able to test my project on Linux till Monday. Masked transparency - is capricious thing. But if transparent bitmaps and image lists (with Masked = true) work on Linux, then there should be way to make it work there. ---------------------------------------------------------------------- _N_ · 2021-09-25 12:12 Thank you for the response. I've installed BGRABitmap and will probably use it, so no need in fixing pure Linux example (only for curiosity). However, I got a new issue with BGRABitmap. I cannot CopyRect from the same image. I was able to copy from the source image, but duplicating of the first row doesn't work and I don't know why: 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 const  SPRITES_PER_ROW = 5;  SPRITES_PER_UNIT = 8;var  SpritesImage: TBGRABitmap;  ResultImage: TBGRABitmap;  SpriteWidth: Integer;  SpriteHeight: Integer;  SrcRect: TRect;  DestRect: TRect;  i: Integer;  c: integer;begin  SpritesImage := TBGRABitmap.Create(SourceFile);  SpriteWidth := SpritesImage.Width div SPRITES_PER_ROW;  SpriteHeight := SpriteData.SpriteHeight;  ResultImage := TBGRABitmap.Create(SpriteWidth * SPRITES_PER_UNIT, SpriteHeight * MAX_PLAYER_NUMBER);  try    // Copy original 5 sprites    SrcRect := TRect.Create(0, 0, SpritesImage.Width, SpriteHeight);    DestRect := TRect.Create(0, 0, SpritesImage.Width, SpriteHeight);    ResultImage.Canvas.CopyRect(DestRect, SpritesImage.Canvas, SrcRect);  // Add missing sprites to the sequence    // It should: 1 2 3 4 5 -> 1 2 3 4 5 4' 3' 2'    for i := 1 to 3 do begin      SrcIndex := SPRITES_PER_ROW - i;      DestIndex := SPRITES_PER_ROW + i;      // Reverse destination x axis to get horizontal flip effect      SrcRect := TRect.Create((SrcIndex - 1) * SpriteWidth, 0, SrcIndex * SpriteWidth, SpriteHeight);      DestRect := TRect.Create(DestIndex * SpriteWidth, 0, (DestIndex - 1) * SpriteWidth, SpriteHeight);      ResultImage.Canvas.CopyRect(DestRect, SpritesImage.Canvas, SrcRect);  end;    // THE CODE BELOW DOES NOTHING, WHY?     // Duplicate first row for each color    SrcRect := TRect.Create(0, 0, ResultImage.Width, SpriteHeight);    for c := 1 to 7 do begin      DestRect := TRect.Create(0, c * SpriteHeight, ResultImage.Width, (c + 1) * SpriteHeight);      ResultImage.Canvas.CopyRect(DestRect, ResultImage.Canvas, SrcRect);    end;        ResultImage.SaveToFile(DestName + '.png']));      finally    SpritesImage.Free();    ResultImage.Free();   end;end; As a workaround, I may create a new BGRABitmap and copy into it, but I'd like to know why it is not possible to copy from the same BGRABitmap (or maybe I just miss something)... ---------------------------------------------------------------------- winni · 2021-09-25 13:21 Hi! Wprking with BGRAbitmap needs a minimum of knowledge of the abilities and the syntax. It is not just another canvas. Some things you need areCode: Pascal  [Select][+][-]window.onload = function(){var x1 = document.getElementById("main_content_section"); if (x1) { var x = document.getElementsByClassName("geshi");for (var i = 0; i  BGRAdest.putimage (x,y, BGRAsource,TDrawMode); The most used  TDrawMode are dmSet or dmDrawWithTransparency. If you need a part of a BGRAbitmap you do: 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 part : TBGRAbitmap;R : TRect;...part := TBGRAbitmap.create;BGRAreplace (part, SourceBGRA.getPart(R)); Have a look in the source of the BGRAbitmap. And read the tutorial: https://wiki.lazarus.freepascal.org/BGRABitmap_tutorial Winni And read about the component TBGRASspriteAnimation:  https://wiki.freepascal.org/BGRASpriteAnimation ---------------------------------------------------------------------- _N_ · 2021-10-16 07:32 Thank you, Winni for your answer and sorry for the delay. I had read the tutorial, but it says nothing about the functions you showed. I employed PutImage that worked for some tasks, but what is still unknown is how to copy a part of a BGRA image into another part of another image (for now even without resampling). As about reading source, it's not that easy and time consuming. I feel like it should be done if one wants to be a guru for that lib or really needs to clarify how a procedure behaves. Instead, I just used autocompletion in IDE which could give some relevant to the problem procedures and the google interesting ones. Also, want to say a note about speed (maybe it will be useful for others). BGRA lib is really fast. It converts a hundred of images (with colors replacing!) in a moment, so now I am in doubt if I need the cache at all. ---------------------------------------------------------------------- circular · 2021-12-29 11:44 To copy a part of an image in BGRABitmap there is PutImagePart. Regards