PDA

View Full Version : How to desaturate image?



reiser
15-07-2014, 12:49 PM
Hello everyone,

my first post here, I got directed to this forum from following StackOverflow (http://stackoverflow.com/questions/24745080/asphyresphinx-how-to-desaturate-image) thread.

I am using AsphyreSphinx framework to draw 2D DirectX scene on form canvas. I am drawing image to form canvas like this:


DXCore.Canvas.UseImage(TableResources.TableImage, TexFull4);
DXCore.Canvas.TexMap(FMetrics.RawTableBounds, clWhite4);

TableResources.TableImage is of TAsphyreImage type. DXCore.Canvas is TAsphyreCanvas. This works fine and draws proper image. However, I have need to also draw black & white version of the same image (desaturate it).

I have tried to use various combinations of parameters in TexMap(), but none gave nothing close to desaturated image. Some things I tried:


DXCore.Canvas.TexMap(FMetrics.RawTableBounds, cGray4(100));
DXCore.Canvas.TexMap(FMetrics.RawTableBounds, cRGB4(100, 100, 100));

There is 4th parameter to function that takes TBlendingEffect enum, I tried various combinations with that too, but without success.

I also tried to manually desaturate image after loading it, with cLerp(), like this:


procedure TTableResources.ImageToGrayscale(const AImage: TAsphyreImage);
var
C1: Integer;
x, y: Integer;
begin
for C1 := 0 to AImage.TextureCount - 1 do
for x := 0 to AImage.Texture[C1].Width - 1 do
for y := 0 to AImage.Texture[C1].Height - 1 do
AImage.Texture[C1].Pixels[x, y] := cLerp(AImage.Texture[C1].Pixels[x, y], cColor(cGrayValue(AImage.Texture[C1].Pixels[x, y])), 0.8);
end;

..but this works awfuly slow (~20-30 seconds to process one image), and doesnt return desired output.

SilverWarrior answered me that this isnt really possible with Asphyre, and I would have to utilize other libraries for that. So my question is, how can I fast desaturate image that is 1200x800px? Is this really not possible with Asphyre (e.g. some combination of color + blending effect in TexMap()?)? Thanks!

Carver413
16-07-2014, 12:23 AM
Function Gray(vColor:TByte4):TByte4;
Var
vAverage:TFlt;
begin
vAverage:=vColor.R*0.3+vColor.G*0.59+vColor.B*0.11 ;
result.R:=Round(vAverage);
result.G:=Round(vAverage);
result.B:=Round(vAverage);
result.A:=vColor.A;
end;

reiser
16-07-2014, 04:33 PM
That wont do it. It produces black image for me (i'm using ImageToGrayscale method i provided in first post, which does Gray() instead cLerp()), and also it's equally slow.

User137
16-07-2014, 05:20 PM
Function Gray(vColor:TByte4):TByte4;
Var
vAverage:TFlt;
begin
vAverage:=vColor.R*0.3+vColor.G*0.59+vColor.B*0.11 ;
result.R:=Round(vAverage);
result.G:=Round(vAverage);
result.B:=Round(vAverage);
result.A:=vColor.A;
end;

Round() would be bottlenecking that

Function Gray(vColor:TByte4):TByte4;
Var
vAverage: byte;
begin
vAverage:=round(vColor.R*0.3+vColor.G*0.59+vColor. B*0.11);
result.R:=vAverage;
result.G:=vAverage;
result.B:=vAverage;
result.A:=vColor.A;
end;

The function should work, if you are getting black you are not using it with right parameters and result value. It's not directly compatible with TColor if that is the type.

laggyluk
16-07-2014, 05:21 PM
I'm not familiar with Asphyre but usualy delphi image classes expose 'scanline' property, it's faster than 'pixels'

reiser
16-07-2014, 07:24 PM
Figured it out guys. I had to use .Lock() on texture, which gave me back pointer to pixels, similar to what ScanLine does. Much faster, and with that gray multiplication trick I made it to work.



procedure TTableResources.DesaturateImage(const AImage: TAsphyreImage);
type
PPixelRec = ^TPixelRec;
TPixelRec = packed record
B: Byte;
G: Byte;
R: Byte;
A: Byte;
end;
var
C1, x, y: Integer;
bitsp: pointer;
pitch: Integer;
bytes_per_pixel: Integer;
pixel: PPixelRec;
gray_value: Byte;
begin
for C1 := 0 to AImage.TextureCount - 1 do
begin
AImage.Texture[C1].Lock(Rect(0, 0, AImage.Texture[C1].Width, AImage.Texture[C1].Height), bitsp, pitch);
try
bytes_per_pixel := pitch div AImage.Texture[C1].Width;
for y := 0 to AImage.Texture[C1].Height - 1 do
for x := 0 to AImage.Texture[C1].Width - 1 do
begin
pixel := PPixelRec(Integer(bitsp) + y * pitch + x * bytes_per_pixel);
gray_value := Round(0.30 * pixel^.r + 0.59 * pixel^.g + 0.11 * pixel^.b);
pixel^.r := gray_value;
pixel^.g := gray_value;
pixel^.b := gray_value;
end;
finally
AImage.Texture[C1].Unlock;
end;
end;
end;


Thanks!

Carver413
16-07-2014, 11:37 PM
Round() would be bottlenecking that
Function Gray(vColor:TByte4):TByte4; Var vAverage: byte; begin vAverage:=round(vColor.R*0.3+vColor.G*0.59+vColor. B*0.11); result.R:=vAverage; result.G:=vAverage; result.B:=vAverage; result.A:=vColor.A; end; The function should work, if you are getting black you are not using it with right parameters and result value. It's not directly compatible with TColor if that is the type. Thanks, I guess I overlooked that.

SilverWarior
17-07-2014, 05:37 AM
Figured it out guys. I had to use .Lock() on texture, which gave me back pointer to pixels, similar to what ScanLine does. Much faster, and with that gray multiplication trick I made it to work.

I'm glad you found solution for this.
You might wanna update the SO question with your answer so other pepole could make use of it.

pitfiend
19-07-2014, 05:15 AM
Did you try http://graphics32.org/wiki/Main/Graphics32? has a lot of filters and image manipulation methods.