PDA

View Full Version : how to generate gradients and textures



paul_nicholls
20-11-2009, 05:10 AM
Hi all, I found this link today on how to generate gradients and textures (png files) using python and python standard libs only!

http://eldarion.com/blog/2009/08/18/how-generate-gradients-and-textures/

I wonder if this is possible to do using Delphi and a zlib unit?

Here is some example Python code that creates png files with gradients:

import zlib
import struct
import array
import random
import colorsys

def output_chunk(out, chunk_type, data):
out.write(struct.pack("!I", len(data)))
out.write(chunk_type)
out.write(data)
checksum = zlib.crc32(data, zlib.crc32(chunk_type))
out.write(struct.pack("!I", checksum))

def get_data(width, height, rgb_func):
fw = float(width)
fh = float(height)
compressor = zlib.compressobj()
data = array.array("B")
for y in range(height):
data.append(0)
fy = float(y)
for x in range(width):
fx = float(x)
data.extend([min(255, max(0, int(v * 255))) for v in rgb_func(fx / fw, fy / fh)])
compressed = compressor.compress(data.tostring())
flushed = compressor.flush()
return compressed + flushed

def write_png(filename, width, height, rgb_func):
out = open(filename, "w")
out.write(struct.pack("8B", 137, 80, 78, 71, 13, 10, 26, 10))
output_chunk(out, "IHDR", struct.pack("!2I5B", width, height, 8, 2, 0, 0, 0))
output_chunk(out, "IDAT", get_data(width, height, rgb_func))
output_chunk(out, "IEND", "")
out.close()


def linear_gradient(start_value, stop_value, start_offset=0.0, stop_offset=1.0):
return lambda offset: (start_value + ((offset - start_offset) / (stop_offset - start_offset) * (stop_value - start_value))) / 255.0

def LINEAR_Y(x, y):
return y

def LINEAR_X(x, y):
return x

def RADIAL(center_x, center_y):
return lambda x, y: (x - center_x) ** 2 + (y - center_y) ** 2

def NO_NOISE(r, g, b):
return r, g, b

def GAUSSIAN(sigma):
def add_noise(r, g, b):
d = random.gauss(0, sigma)
return r + d, g + d, b + d
return add_noise

def HSV(h, s, v):
r, g, b = colorsys.hsv_to_rgb(h, s, v)
return 255 * r, 255 * g, 255 * b

def gradient(value_func, noise_func, DATA):
def gradient_function(x, y):
initial_offset = 0.0
v = value_func(x, y)
for offset, start, end in DATA:
if v < offset:
r = linear_gradient(start[0], end[0], initial_offset, offset)(v)
g = linear_gradient(start[1], end[1], initial_offset, offset)(v)
b = linear_gradient(start[2], end[2], initial_offset, offset)(v)
return noise_func(r, g, b)
initial_offset = offset
return noise_func(end[0] / 255.0, end[1] / 255.0, end[2] / 255.0)
return gradient_function


cheers,
Paul

code_glitch
22-11-2009, 09:42 AM
I'm no expert but i'm not seeing many things on there that seems to rely on the python libs you mentioned. Isn't there an SDL/OpenGL equivalent for textures from png and bmp files. I think I may have remebered reading about it in some tutorial but can't remember which unfortunately, becuase it was some time ago, but I do think the page background was black if it's of any help. ???

paul_nicholls
22-11-2009, 12:02 PM
I'm only going on that the website mentioned it generated png files using standard python libs, and no other dependencies...

I just thought it was really neat that a simple png file could be created that way using the zlib library :)

cheers,
Paul

code_glitch
22-11-2009, 01:06 PM
I must say that it is useful... My main concern if you were going to use it would be efficiency. I know how surfaces dont like hi res but you are going to have some very HD content for good textures so how is it going to perform? On top of that python is kinda high evel so not too eficient (although better than java..)

just wondering.

paul_nicholls
22-11-2009, 07:36 PM
I must say that it is useful... My main concern if you were going to use it would be efficiency. I know how surfaces dont like hi res but you are going to have some very HD content for good textures so how is it going to perform? On top of that python is kinda high evel so not too eficient (although better than java..)

just wondering.


:)

Obviously, I would prefer to do this in Delphi and not Python ;)
If I was going to do gradients, I also wouldn't do huge textures either...

cheers,
Paul

noeska
22-11-2009, 09:37 PM
Have a look on how i do this in glvg.

deathshadow
08-12-2009, 07:01 AM
A while ago on another forums we were talking about doing this with php - not so much to do it for production use, but as a general coding exercise... What I came up with was this:


<?php

function styleGradient($width,$height,$color) {

$image=imagecreatetruecolor($width,$height);

$scaleWidth=$width-1;
$scaleHeight=$height-1;

if (is_array($color[0])) {
$r=$color[0][0];
$g=$color[0][1];
$b=$color[0][2];
} else {
$r=($color[0] >> 16) & 0xFF;
$g=($color[0] >> 8) & 0xFF;
$b=$color[0] & 0xFF;
}

if (is_array($color[3])) {
$endR=$color[3][0];
$endG=$color[3][1];
$endB=$color[3][2];
} else {
$endR=($color[3] >> 16) & 0xFF;
$endG=($color[3] >> 8) & 0xFF;
$endB=$color[3] & 0xFF;
}

$incR=($endR-$r)/$scaleHeight;
$incG=($endG-$g)/$scaleHeight;
$incB=($endB-$b)/$scaleHeight;

$leftColors=array();

for ($y=0; $y<=$height; $y++) {
$leftColors[$y]=array($r,$g,$b);
$r+=$incR;
$g+=$incG;
$b+=$incB;
}

if (is_array($color[1])) {
$endR=$color[2][0];
$endG=$color[2][1];
$endB=$color[2][2];
} else {
$r=($color[1] >> 16) & 0xFF;
$g=($color[1] >> 8) & 0xFF;
$b=$color[1] & 0xFF;
}

if (is_array($color[2])) {
$r=$color[1][0];
$g=$color[1][1];
$b=$color[1][2];
} else {
$endR=($color[2] >> 16) & 0xFF;
$endG=($color[2] >> 8) & 0xFF;
$endB=$color[2] & 0xFF;
}

$incR=($endR-$r)/$scaleHeight;
$incG=($endG-$g)/$scaleHeight;
$incB=($endB-$b)/$scaleHeight;

$rightColors=array();

for ($y=0; $y<$height; $y++) {
$rightColors[$y]=array($r,$g,$b);
$r+=$incR;
$g+=$incG;
$b+=$incB;
}

$style=array();

for ($y=0; $y<$height; $y++) {
$r=$leftColors[$y][0];
$g=$leftColors[$y][1];
$b=$leftColors[$y][2];

$incR=($rightColors[$y][0]-$r)/$scaleWidth;
$incG=($rightColors[$y][1]-$g)/$scaleWidth;
$incB=($rightColors[$y][2]-$b)/$scaleWidth;

for ($x=0; $x<$width; $x++) {
$style[$x]=imagecolorallocate($image,$r,$g,$b);
$r+=$incR;
$g+=$incG;
$b+=$incB;
}

imagesetstyle($image,$style);
imageline($image,0,$y,$scaleWidth,$y,IMG_COLOR_STY LED);
}
return $image;
}

$width=is_numeric($_GET['w']) ? $_GET['w'] : 256;
$height=is_numeric($_GET['h']) ? $_GET['h'] : 256;
$color=array(
empty($_GET['c1']) ? 0x000000 : hexdec($_GET['c1']),
empty($_GET['c2']) ? 0xFF0000 : hexdec($_GET['c2']),
empty($_GET['c3']) ? 0x00FF00 : hexdec($_GET['c3']),
empty($_GET['c4']) ? 0x0000FF : hexdec($_GET['c4'])
);

header("Content-type: image/png");
$image=styleGradient($width,$height,$color);
imagepng($image);
imagedestroy($image);

?>

Which works pretty good at a different color at each corner gradient. First I build a buffer line that contains the colors down the left side, then I do it for the colors on the right side, and then I go between the two points...

Let's see, written up for free pascal (using SDL for expediency) that would be...



unit SDLGradient;

{$COPERATORS ON}

interface

uses
sdl;

type
t4Colors=array[0..3] of dword;
tColorQuad=packed record
b,g,r,a:byte;
end;

function SDL_Gradient(width,height:longint; color:t4Colors):pSDL_Surface;

implementation

function SDL_Gradient(width,height:longint; color:t4Colors):pSDL_Surface;
var
scaleWidth,scaleHeight:longint;

sideBuffersize:dword;

r,g,b,a,
endR,endG,endB,endA,
incR,incG,incB,incA:double;

tempSurface:pSDL_Surface;

leftColors,rightColors,
lPoint,rPoint,bPoint,
lPointEnd,rPointEnd,bPointEnd:^tColorQuad;

begin
tempSurface:=SDL_CreateRGBSurface(SDL_SWSURFACE,wi dth,height,32,0,0,0,0);

scaleWidth:=width-1;
scaleHeight:=height-1;

sideBufferSize:=height*4;

r:=color[0] shr 24;
g:=(color[0] shr 16) and $FF;
b:=(color[0] shr 8) and $FF;
a:=color[0] and $FF;

endR:=color[3] shr 24;
endG:=(color[3] shr 16) and $FF;
endB:=(color[3] shr 8) and $FF;
endA:=color[3] and $FF;

incR:=(endR-r)/scaleHeight;
incG:=(endG-g)/scaleHeight;
incB:=(endB-b)/scaleHeight;
incA:=(endA-a)/scaleHeight;

getmem(leftColors,sideBufferSize);
getmem(rightColors,sideBufferSize);

lPoint:=leftColors;
lPointEnd:=lPoint+height;

while (lpoint<lPointEnd) do begin
lPoint^.r:=trunc(r);
lPoint^.g:=trunc(g);
lPoint^.b:=trunc(b);
lPoint^.a:=trunc(a);
inc(lPoint);
r+=incR;
g+=incG;
b+=incB;
a+=incA;
end;

r:=color[1] shr 24;
g:=(color[1] shr 16) and $FF;
b:=(color[1] shr 8) and $FF;
a:=color[1] and $FF;

endR:=color[2] shr 24;
endG:=(color[2] shr 16) and $FF;
endB:=(color[2] shr 8) and $FF;
endA:=color[2] and $FF;

incR:=(endR-r)/scaleHeight;
incG:=(endG-g)/scaleHeight;
incB:=(endB-b)/scaleHeight;
incA:=(endA-a)/scaleHeight;

rPoint:=rightColors;
rPointEnd:=rPoint+height;

while rPoint<rPointEnd do begin
rPoint^.r:=trunc(r);
rPoint^.g:=trunc(g);
rPoint^.b:=trunc(b);
rPoint^.a:=trunc(a);
inc(rPoint);
r+=incR;
g+=incG;
b+=incB;
a+=incA;
end;

lPoint:=leftColors;
rPoint:=rightColors;
bPoint:=pointer(tempSurface^.pixels);

while (lPoint<lPointEnd) do begin
r:=lPoint^.r;
g:=lPoint^.g;
b:=lPoint^.b;
a:=lPoint^.a;

inc(lPoint);

incR:=(rPoint^.r-r)/scaleWidth;
incG:=(rPoint^.g-g)/scaleWidth;
incB:=(rPoint^.b-b)/scaleWidth;
incA:=(rPoint^.a-a)/scaleWidth;

inc(rPoint);

bPointEnd:=bPoint+width;

while (bPoint<bPointend) do begin
bPoint^.r:=trunc(r);
bPoint^.g:=trunc(g);
bPoint^.b:=trunc(b);
bPoint^.a:=trunc(a);
inc(bPoint);
r+=incR;
g+=incG;
b+=incB;
a+=incA;
end;

end;

freemem(rightColors);
freemem(leftColors);

SDL_Gradient:=tempSurface;

end;

end.

Which renders to a nice little SDL surface so you can save it, show it, whatever. Right now it uses floats, which is kind-of messy/slow. Might be better to use fixed integer based on the dimensions being created, or possibly Bresenham algorithm - I'd probably go with the former since the notion of having four separate line-drawing algos running simultaneously just sounds like a really bad idea.

One cool modification I made while in there was to make it also do alpha per corner... just another parameter really.

Can demo that real quick thus:

program demoGradient;

{$COPERATORS ON}
{$APPTYPE GUI}

uses
SDL,
SDLGradient;

const
demoGradientColor:t4Colors=(
$FF0000FF,
$00FF00FF,
$0000FFFF,
$FFFFFFFF
);

var
screen:pSDL_SURFACE;
SDLTestEvent:pSDL_EVENT;

procedure showGradient;
var
gradient:pSDL_SURFACE;
gradientRect,screenRect:pSDL_RECT;
begin
gradient:=SDL_Gradient(512,512,demoGradientColor);
new(gradientRect);
with gradientRect^ do begin
x:=0;
y:=0;
w:=512;
h:=512;
end;
new(screenRect);
with screenRect^ do begin
x:=0;
y:=0;
w:=512;
h:=512;
end;
SDL_BLITSURFACE(gradient,gradientRect,screen,scree nRect);
SDL_FLIP(screen);
dispose(screenRect);
dispose(gradientRect);
SDL_FREESURFACE(gradient);
end;

function SDL_poll:boolean;
var
windowEnd:boolean;
begin
windowEnd:=false;
if SDL_pollEvent(SDLTestEvent)=1 then begin
case SDLTestEvent^.type_ of
SDL_KEYDOWN:begin
if (SDLTestEvent^.key.keysym.sym=SDLK_ESCAPE) then begin
windowEnd:=true;
end;
end;
SDL_QUITEV:begin
windowEnd:=true;
end;
end;
end;
SDL_poll:=windowEnd;
end;

begin
SDL_INIT(SDL_INIT_VIDEO);
screen:=SDL_SETVIDEOMODE(512,512,32,SDL_SWSURFACE) ;
if screen=nil then begin
writeln('Failed to initialize SDL video');
end else begin
showGradient;
new(SDLTestEvent);
repeat until SDL_poll;
SDL_FREESURFACE(screen);
SDL_QUIT;
end;
end.

Let's see... bah, you don't have attachments enabled. That's ok, I'll just upload the source and a win32 compiled .exe in a rar file to my server.

http://www.cutcodedown.com/fpcDemoCode/gradients.rar

Enjoy - if anyone actually finds a USE for that, well, more power to you.

A better version would probably use integer math, and have detection for if you are doing horizontal or vertical gradients instead of unique corners, since those could be generated a heck of a lot faster than how I did this here.

-- edit -- oops, already had someone ask about SDL.DLL on that, so I added it to the .rar

paul_nicholls
08-12-2009, 11:46 PM
Nice, thanks :)

cheers,
Paul

deathshadow
09-12-2009, 06:59 AM
Oh, and in porting that code to pascal/SDL, after almost eight years of not really doing ANY real programming in pascal, and working almost entirely in PHP/mySQL/HTML/CSC/JS

I just need to say...

GOD BLESS POINTERS, RECORDS and an OBJECT MODEL THAT MAKES SENSE!!!

It's like coming home from a deployment.