[MODULE] DynamicSprite resizing, rotating & warping using filters

Started by Khris, Thu 19/08/2010 03:22:58

Previous topic - Next topic

Khris

Resize/rotate/warp DynamicSprites using a bilinear filter!


Available commands:

  DynamicSprite.ResizeFilter(int width, int height, optional Filter filter);
  - To keep the aspect ratio, pass 0 for either width or height.

  DynamicSprite.RotateFilter(float angle, optional Filter filter);

  DynamicSprite.ResNRotFilter(int w, int h, float angle, optional Filter filter);
  - To keep the aspect ratio, pass 0 for either width or height.

  DynamicSprite.Warp(int x1, int y1, int x2, int y2, int x3, int y3, int x4, int y4, optional Filter filter);
  - moves UL corner to x1;y1, UR corner to x2;y2, BL corner to x3,y3, BR corner to x4;y4

  - filter is either eFilterBilinear (the default), eFilterLinear or eFilterNearestNeighbor.


Example code:
  DynamicSprite*copy = DynamicSprite.CreateFromExistingSprite(original.Graphic);

  // resize the sprite to half its size, then rotate it clockwise by 30 degrees using the bilinear filter
  copy.ResNRotFilter(0, copy.Height/2, 30.0);


Visual examples:

 
  (the red boxes just illustrate the transformations, they are not drawn by the module :))


Get it here:  DOWNLOAD

Knox

Hey Khris,

Cool module man, where's your paypal info!? :P

Ok, I did some tests on a 32 bit png image with transparency (a watch with a semi-transparent shadow under it, and full transparency around the watch:



(Original image, 120x120, png with transparency)

...Since there's no magic pink color I commented out line 185:
Code: ags

//if (c == -1) c = magic_pink;

and removed "magic_pink" in line 200
Code: ags

do.Clear();

to have my resized sprite keep its transparency.

Here are the results:



It seems to me "Bilinear" is the closest to Photoshop's resize (mostly inside the watch), however is there a way to have the semi-transparent shadow get slightly better results, and it's contour? If the contour of the watch and the shadow of the watch was slightly more blurred to match the quality you succeeded in getting for the "inside" of the watch sprite, it would be absolutely PERFECT... ;D...

Is there any way to get that?



--All that is necessary for evil to triumph is for good men to do nothing.

Khris

The module doesn't support alpha channels at all yet; I'm not sure if that's even possible without having to move to a plugin.
What the module does is calculate the color of a pixel by looking at the four surrounding ones of the original sprite. If transparency is encountered and outweighs color, the "resized" pixel also becomes transparent. Otherwise the color pixels determine the final color. Thus the resized sprite still has crisp edges and maintains the surrounding transparency.

The only way to use my module while keeping semi-transparent stuff is to split the sprite into parts. If you resized the watch and shadow separately, you could probably combine them afterwards, using semi-transparency for the shadow. Not sure if you can without using Calin's AGS Blend plugin though, at least if you want to move it around, not just paint it onto something.

Calin Leafshade

AGS doesnt allow direct access to the alpha channel so the alpha channel cant be messed with and is flattened by the module.

Youd need to use my plugin to get the effect you want.

Knox

Ah ok I see then!

Well, is there a way to intergrate Khris' resize module with Calin's plugin  :-X

:=
--All that is necessary for evil to triumph is for good men to do nothing.

Calin Leafshade

yea, my plugin has a GetAlpha and PutAlpha funtion which can be used in exactly the same way as getpixel and draw pixel so it should be relatively simple to do... although it might be a little awkward since you cant have the drawing surface locked at the same time as you use my plugin i dont think so youd have to do all the RGB values, release the surface and then do the alpha values from a copy.

Knox

Hi guys,

Im pretty sure I get most of what you suggested (from this thread and the other one: http://www.adventuregamestudio.co.uk/yabb/index.php?topic=41664.20)

Quote
Yea, although I looked through khris' module and I'm not 100% sure how it works... I are not as clever as Khris.

But!

As a very poor work around you could make a copy of the sprite using the GetAlpha and then run khris resize on that and then once it has been resized, plug the values back into the sprite.. so something like this.

Make a copy of your sprite at full size
iterate through each pixel of the original using GetAlpha to get its value.
Draw on the copy a grey value representing the alpha.
like:

Code:
alpha = GetAlpha(orig.Graphic, x, y);
ds.DrawingColor = Game.GetColorFromRGB(alpha,alpha,alpha); //this is grey
ds.DrawPixel(x,y);


then resize both your original and copy(which contains the alphas)

then iterate through the pixels again putting the alpha back into the original in accordance with the grey values stored in the copy.

This method might not be as inefficient as it seems since I dont think you can keep the drawingsurface open whilst running GetAlpha or PutAlpha so this way you only have to get/release the drawing surfaces a handful of times.

...to copy the alpha pixel by pixel from a sprite and paste it into a dynamic sprite...resize with Khris' module (bilinear) and then getting the gray values of the bilinear resized "alpha sprite" and recopying that into the final cursor image with Calin's plugin (PutAlpha)...

...the only thing I dont know how to do is how to write the code part for running/iterating through each pixel (Im guessing some sort of matrix thing, like (0,0), then (0,1), then (0,2)...etc...for a 120x120 sprite.

Ive got the base here:
Code: ags

    cg = player.ActiveInventory.Graphic;
    inv_curs = DynamicSprite.CreateFromExistingSprite(cg, true);   // create sprite
    
    alphaSprite = DynamicSprite.CreateFromExistingSprite(cg, true);

    //get alpha "color" for each pixel of the 120x120 image 
    int x = 0;
    int y = 0;
    while (x <= some code here to process each pixel in a 120x120 image)
   {
      alpha = GetAlpha(cg.Graphic, x, y);
      DrawingSurface *ds = alphaSprite.GetDrawingSurface();
      ds.DrawingColor = Game.GetColorFromRGB(alpha,alpha,alpha); //this is grey
      ds.DrawPixel(x,y);
    }

    //resize both the cursor sprite and the "alpha sprite" with Bilinear
    alphaSprite.ResizeFilter(alphaSprite.Width/2, alphaSprite.Height/2, eFilterBilinear);
    inv_curs.ResizeFilter(inv_curs.Width/2, inv_curs.Height/2, eFilterBilinear);

    //copy the alpha values of the "alpha sprite" into the cursor sprite for each pixel
    int x = 0;
    int y = 0;
    while x <= (some code here to process each pixel in a 120x120 image)
   {
      alpha = GetAlpha(alphaSprite.Graphic, x, y);
      PutAlpha((inv_curs.Graphic, x, y, alpha);
    }


Im not too sure how to run through each pixel so its nice + efficiently written...Do I use a "while" command with the x and y position of each pixel?  :)
--All that is necessary for evil to triumph is for good men to do nothing.

Calin Leafshade

you have 2 nested while loops which iterate through line by line

like this

Code: ags

int x, y;

while (y < height){

      while (x < width){

         // code referencing x and y here
       x++;
      }

  y++;
  x = 0;
}

Knox

#8
Just to debug, I drew the black and white alpha pixels directly to the cursor, to see how it works:
Code: ags

    cg = player.ActiveInventory.Graphic;
    inv_curs = DynamicSprite.CreateFromExistingSprite(cg, true);       
  
    alphaSprite = DynamicSprite.CreateFromExistingSprite(cg, true);
    //get alpha "color" for each pixel of the 120x120 image 
    int x, y, alpha;
    while (y < alphaSprite.Height)
    {
      while (x < alphaSprite.Width)
      {
        //code referencing x and y here
        alpha = GetAlpha(cg, x, y);        
        DrawingSurface *ds = inv_curs.GetDrawingSurface();
        ds.DrawingColor = Game.GetColorFromRGB(alpha,alpha,alpha); //this is grey
        ds.DrawPixel(x,y);
        x++;
      }
      y++;
      x = 0;
    }


...it seems (unless I did something wrong) that it draws the alpha rotated 90 degrees clockwise, then flipped:





Besides that, in theory, the resized alpha image below will be WAY better with Khris' bilinear filter:


--All that is necessary for evil to triumph is for good men to do nothing.

Calin Leafshade

Ah thats my fault.

The GetAlpha and PutAlpha reference the x and y values the other way round just swap x and y round and it should work.. i'll fix in the next release

Knox

Wow, a big thanks to you guys...combining Calin's plugin with Khris' module (ASB) permits the resizing of images with a nice bilinear filter on a 32 bit alpha sprite, with a nice blur on the alpha edges:

Resized 50% with...


        1) AGS        2) ASB (alpha-swap, bilinear)           3)  Photoshop CS3


Thanks guys!
--All that is necessary for evil to triumph is for good men to do nothing.

Khris

Hey great, very nice work combining the two!

I added some stuff to my module, now you can also rotate and even warp the sprite :)

(See first post)

Calin Leafshade

Thats very very cool khris.

Any chance of a bicubic filter? The algorithm is *way* too complex for me to figure out.

Khris

I'll try to explain:

Once I have translated the coords of the target sprite (through which I iterate) to float coords of the original sprite, I check if I hit an original pixel exactly (distance = 0). This usually happens only for the corners or if the zoom factor is an integer.

In most cases though I land in between. The classic bilinear algorithm (as I learned yesterday) simply averages out the surrounding four pixels. When I wrote the first version, I decided to weigh them differently according to their distance.

So the next step is to loop through the surrounding pixels and store their color and distance in a struct. Actually, I store the inverse of the distance's square (.part) to give close pixels considerably more weight than more remote ones. (The weight distribution directly influences the sharpness.)
The first thing to do before mixing the RGB values is checking for transparency. A transparent pixel adds -.part, an opaque one adds .part. If the result is negative, transparency outweighs color and the final pixels ends up being transparent.
Otherwise, the opaque pixels are mixed to generate the final color. This is done by averaging the RGB values times .part.

Bicubic uses the surrounding 16 pixels. Implementing that can be done using a few minor adjustments. It'll make the whole process considerably slower though. IMO the difference to bilinear is marginal; it provides better results only for zooming in x3 or higher.
Unfortunately, my module is pretty slow as it is without much room for optimization. I considered writing a plugin instead but I have never actually coded in C++ so that's probably not gonna happen in the foreseeable future.

Calin Leafshade

using c++ for simple sprite stuff is pretty indistinguishable from AGSScript.

essentially the API can dump a sprite to a 2D array of long ints which is very easy to manipulate however you want to.

However I have no idea how to adjust the size of a sprite of if thats even possible with the API.

Knox

QuoteUnfortunately, my module is pretty slow as it is without much room for optimization

Ok, so the module is pretty much as fast as it can get right now? If the bilinear filter was inside a plugin, it could be faster?
--All that is necessary for evil to triumph is for good men to do nothing.

Calin Leafshade

yea, essentially its because the DrawPixel routine is pretty slow. I'm sure why that is I assume its something to do with memory access maybe.

In a plugin you can *directly* access the sprite as a piece of memory. There is no shuffling between APIs and stuff so it is much faster.

Thats my understanding of it anyway.

Knox

Well if the module can be optimized by like .5 seconds, it would be worth it...if not, if someone was able/willing to convert Khris' bilinear filter into a plugin Im sure people like me would be willing to offer a reward (artwork, monies or sexual favors*)

*this part is not true
--All that is necessary for evil to triumph is for good men to do nothing.

Calin Leafshade


Knox

0.5 seconds...heh, thats pretty approximate

The way Im using it now probably isnt a really good idea: Im using it for mouse cursors, so depending on what the mouse hovers over, I redraw the mouse cursor with various dynamic sprites....and using the module + plugin I get a slight delay to update the mouse cursor (like maybe .75 to 1.0 second...but I dont have a nano-second stopwatch or anything :P)

--All that is necessary for evil to triumph is for good men to do nothing.

SMF spam blocked by CleanTalk