--- a/hedgewars/uTextures.pas Mon Jun 25 16:22:03 2012 +0200
+++ b/hedgewars/uTextures.pas Tue Jul 10 11:08:35 2012 +0200
@@ -24,6 +24,7 @@
function NewTexture(width, height: Longword; buf: Pointer): PTexture;
procedure Surface2GrayScale(surf: PSDL_Surface);
+function SurfaceSheet2Atlas(surf: PSDL_Surface; spriteWidth: Integer; spriteHeight: Integer): PTexture;
function Surface2Atlas(surf: PSDL_Surface; enableClamp: boolean): PTexture;
procedure FreeTexture(tex: PTexture);
procedure ComputeTexcoords(texture: PTexture; r: PSDL_Rect; tb: PVertexRect);
@@ -32,10 +33,164 @@
procedure freeModule;
implementation
-uses GLunit, uUtils, uVariables, uConsts, uDebug, uConsole, uAtlas;
+uses GLunit, uUtils, uVariables, uConsts, uDebug, uConsole, uAtlas, SysUtils;
+
+var
+ logFile: TextFile;
+
+function CropSurface(source: PSDL_Surface; rect: PSDL_Rect): PSDL_Surface;
+var
+ fmt: PSDL_PixelFormat;
+ srcP, dstP: PByte;
+ copySize: Integer;
+ i: Integer;
+const
+ pixelSize = 4;
+begin
+ //writeln(stdout, 'Cropping from ' + IntToStr(source^.w) + 'x' + IntToStr(source^.h) + ' -> ' + IntToStr(rect^.w) + 'x' + IntToStr(rect^.h));
+
+ fmt:= source^.format;
+
+ CropSurface:= SDL_CreateRGBSurface(source^.flags, rect^.w, rect^.h,
+ fmt^.BitsPerPixel, fmt^.Rmask, fmt^.Gmask, fmt^.Bmask, fmt^.Amask);
+
+ if SDL_MustLock(source) then
+ SDLTry(SDL_LockSurface(source) >= 0, true);
+ if SDL_MustLock(CropSurface) then
+ SDLTry(SDL_LockSurface(CropSurface) >= 0, true);
+
+ srcP:= source^.pixels;
+ dstP:= CropSurface^.pixels;
+
+ inc(srcP, pixelSize * rect^.x);
+ inc(srcP, source^.pitch * rect^.y);
+ copySize:= rect^.w * pixelSize;
+ for i:= 0 to Pred(rect^.h) do
+ begin
+ Move(srcP^, dstP^, copySize);
+ inc(srcP, source^.pitch);
+ inc(dstP, CropSurface^.pitch);
+ end;
+
+ if SDL_MustLock(source) then
+ SDL_UnlockSurface(source);
+ if SDL_MustLock(CropSurface) then
+ SDL_UnlockSurface(CropSurface);
+end;
+
+function TransparentLine(p: PByte; stride: Integer; length: Integer): boolean;
+var
+ i: Integer;
+begin
+ TransparentLine:= false;
+ for i:=0 to pred(length) do
+ begin
+ if p^ <> 0 then
+ exit;
+ inc(p, stride);
+ end;
+ TransparentLine:= true;
+end;
+
+function AutoCrop(source: PSDL_Surface; var cropinfo: TCropInformation): PSDL_Surface;
+var
+ l,r,t,b, i: Integer;
+ pixels, p: PByte;
+ scanlineSize: Integer;
+ rect: TSDL_Rect;
+const
+ pixelSize = 4;
+begin
+ l:= source^.w;
+ r:= 0;
+ t:= source^.h;
+ b:= 0;
+
+ if SDL_MustLock(source) then
+ SDLTry(SDL_LockSurface(source) >= 0, true);
+
+ pixels:= source^.pixels;
+ scanlineSize:= source^.pitch;
-var TextureList: PTexture;
+ inc(pixels, 3); // advance to alpha value
+
+ // check top
+ p:= pixels;
+ for i:= 0 to Pred(source^.h) do
+ begin
+ if not TransparentLine(p, pixelSize, source^.w) then
+ begin
+ t:= i;
+ break;
+ end;
+ inc(p, scanlineSize);
+ end;
+
+
+ // check bottom
+ p:= pixels;
+ inc(p, scanlineSize * source^.h);
+ for i:= 0 to Pred(source^.h - t) do
+ begin
+ dec(p, scanlineSize);
+ if not TransparentLine(p, pixelSize, source^.w) then
+ begin
+ b:= i;
+ break;
+ end;
+ end;
+
+ // check left
+ p:= pixels;
+ for i:= 0 to Pred(source^.w) do
+ begin
+ if not TransparentLine(p, scanlineSize, source^.h) then
+ begin
+ l:= i;
+ break;
+ end;
+ inc(p, pixelSize);
+ end;
+ // check right
+ p:= pixels;
+ inc(p, scanlineSize);
+ for i:= 0 to Pred(source^.w - l) do
+ begin
+ dec(p, pixelSize);
+ if not TransparentLine(p, scanlineSize, source^.h) then
+ begin
+ r:= i;
+ break;
+ end;
+ end;
+
+ if SDL_MustLock(source) then
+ SDL_UnlockSurface(source);
+
+ rect.x:= l;
+ rect.y:= t;
+
+ rect.w:= source^.w - r - l;
+ rect.h:= source^.h - b - t;
+
+ cropInfo.l:= l;
+ cropInfo.r:= r;
+ cropInfo.t:= t;
+ cropInfo.b:= b;
+ cropInfo.x:= Trunc(source^.w / 2 - l + r);
+ cropInfo.y:= Trunc(source^.h / 2 - t + b);
+
+ if (l = source^.w) or (t = source^.h) then
+ begin
+ result:= nil;
+ exit;
+ end;
+
+ if (l <> 0) or (r <> 0) or (t <> 0) or (b <> 0) then
+ result:= CropSurface(source, @rect)
+ else result:= source;
+end;
procedure SetTextureParameters(enableClamp: Boolean);
begin
@@ -49,87 +204,80 @@
end;
procedure ComputeTexcoords(texture: PTexture; r: PSDL_Rect; tb: PVertexRect);
-var x0, y0, x1, y1, tmp: Real;
+var
+ x0, y0, x1, y1, tmp: Real;
w, h, aw, ah: LongInt;
-const texelOffset = 0.0;
+ p: PChar;
+const
+ texelOffset = 0.0;
begin
-aw:=texture^.atlas^.w;
-ah:=texture^.atlas^.h;
+ aw:=texture^.atlas^.w;
+ ah:=texture^.atlas^.h;
-if texture^.isRotated then
+ if texture^.isRotated then
begin
- w:=r^.h;
- h:=r^.w;
- end
-else
+ w:=r^.h;
+ h:=r^.w;
+ end else
begin
- w:=r^.w;
- h:=r^.h;
+ w:=r^.w;
+ h:=r^.h;
end;
-x0:= (texture^.x + r^.x + texelOffset)/aw;
-x1:= (texture^.x + r^.x + w - texelOffset)/aw;
-y0:= (texture^.y + r^.y + texelOffset)/ah;
-y1:= (texture^.y + r^.y + h - texelOffset)/ah;
+ x0:= (texture^.x + {r^.x} + texelOffset)/aw;
+ x1:= (texture^.x + {r^.x} + w - texelOffset)/aw;
+ y0:= (texture^.y + {r^.y} + texelOffset)/ah;
+ y1:= (texture^.y + {r^.y} + h - texelOffset)/ah;
-if (texture^.isRotated) then
-begin
- tb^[0].X:= x0;
- tb^[0].Y:= y0;
- tb^[3].X:= x1;
- tb^[3].Y:= y0;
- tb^[2].X:= x1;
- tb^[2].Y:= y1;
- tb^[1].X:= x0;
- tb^[1].Y:= y1
-end else
-begin
- tb^[0].X:= x0;
- tb^[0].Y:= y0;
- tb^[1].X:= x1;
- tb^[1].Y:= y0;
- tb^[2].X:= x1;
- tb^[2].Y:= y1;
- tb^[3].X:= x0;
- tb^[3].Y:= y1;
-end;
+ if (texture^.isRotated) then
+ begin
+ tb^[0].X:= x0;
+ tb^[0].Y:= y0;
+ tb^[3].X:= x1;
+ tb^[3].Y:= y0;
+ tb^[2].X:= x1;
+ tb^[2].Y:= y1;
+ tb^[1].X:= x0;
+ tb^[1].Y:= y1
+ end else
+ begin
+ tb^[0].X:= x0;
+ tb^[0].Y:= y0;
+ tb^[1].X:= x1;
+ tb^[1].Y:= y0;
+ tb^[2].X:= x1;
+ tb^[2].Y:= y1;
+ tb^[3].X:= x0;
+ tb^[3].Y:= y1;
+ end;
end;
procedure ResetVertexArrays(texture: PTexture);
var r: TSDL_Rect;
begin
-with texture^ do
-begin
- vb[0].X:= 0;
- vb[0].Y:= 0;
- vb[1].X:= w;
- vb[1].Y:= 0;
- vb[2].X:= w;
- vb[2].Y:= h;
- vb[3].X:= 0;
- vb[3].Y:= h;
-end;
+ with texture^ do
+ begin
+ vb[0].X:= texture^.cropInfo.l;
+ vb[0].Y:= texture^.cropInfo.t;
+ vb[1].X:= texture^.cropInfo.l + w;
+ vb[1].Y:= texture^.cropInfo.t;
+ vb[2].X:= texture^.cropInfo.l + w;
+ vb[2].Y:= texture^.cropInfo.t + h;
+ vb[3].X:= texture^.cropInfo.l;
+ vb[3].Y:= texture^.cropInfo.t + h;
+ end;
-r.x:= 0;
-r.y:= 0;
-r.w:= texture^.w;
-r.h:= texture^.h;
-ComputeTexcoords(texture, @r, @texture^.tb);
+ r.x:= 0;
+ r.y:= 0;
+ r.w:= texture^.w;
+ r.h:= texture^.h;
+ ComputeTexcoords(texture, @r, @texture^.tb);
end;
function NewTexture(width, height: Longword; buf: Pointer): PTexture;
begin
new(NewTexture);
-NewTexture^.PrevTexture:= nil;
-NewTexture^.NextTexture:= nil;
NewTexture^.Scale:= 1;
-if TextureList <> nil then
- begin
- TextureList^.PrevTexture:= NewTexture;
- NewTexture^.NextTexture:= TextureList
- end;
-TextureList:= NewTexture;
-
// Atlas allocation happens here later on. For now we just allocate one exclusive atlas per sprite
new(NewTexture^.atlas);
@@ -142,6 +290,14 @@
NewTexture^.isRotated:=false;
NewTexture^.shared:=false;
NewTexture^.surface:=nil;
+NewTexture^.nextFrame:=nil;
+NewTexture^.cropInfo.l:= 0;
+NewTexture^.cropInfo.r:= 0;
+NewTexture^.cropInfo.t:= 0;
+NewTexture^.cropInfo.b:= 0;
+NewTexture^.cropInfo.x:= width div 2;
+NewTexture^.cropInfo.y:= height div 2;
+
ResetVertexArrays(NewTexture);
@@ -175,26 +331,86 @@
end;
+function SurfaceSheet2Atlas(surf: PSDL_Surface; spriteWidth: Integer; spriteHeight: Integer): PTexture;
+var
+ subSurface: PSDL_Surface;
+ framesX, framesY: Integer;
+ last, current: PTexture;
+ r: TSDL_Rect;
+ x, y: Integer;
+begin
+ SurfaceSheet2Atlas:= nil;
+ r.x:= 0;
+ r.y:= 0;
+ r.w:= spriteWidth;
+ r.h:= spriteHeight;
+ last:= nil;
+
+ framesX:= surf^.w div spriteWidth;
+ framesY:= surf^.h div spriteHeight;
+
+ for x:=0 to Pred(framesX) do
+ begin
+ r.y:= 0;
+ for y:=0 to Pred(framesY) do
+ begin
+ subSurface:= CropSurface(surf, @r);
+ current:= Surface2Atlas(subSurface, false);
+
+ if last = nil then
+ begin
+ SurfaceSheet2Atlas:= current;
+ last:= current;
+ end else
+ begin
+ last^.nextFrame:= current;
+ last:= current;
+ end;
+ inc(r.y, spriteHeight);
+ end;
+ inc(r.x, spriteWidth);
+ end;
+
+ SDL_FreeSurface(surf);
+end;
+
function Surface2Atlas(surf: PSDL_Surface; enableClamp: boolean): PTexture;
var tw, th, x, y: Longword;
tmpp: pointer;
+ cropped: PSDL_Surface;
fromP4, toP4: PLongWordArray;
+ cropInfo: TCropInformation;
begin
- if (surf^.w <= 256) and (surf^.h <= 256) then
+ cropped:= AutoCrop(surf, cropInfo);
+ if cropped <> surf then
+ begin
+ SDL_FreeSurface(surf);
+ surf:= cropped;
+ end;
+
+ if surf = nil then
+ begin
+ new(Surface2Atlas);
+ Surface2Atlas^.w:= 0;
+ Surface2Atlas^.h:= 0;
+ Surface2Atlas^.x:=0 ;
+ Surface2Atlas^.y:=0 ;
+ Surface2Atlas^.isRotated:= false;
+ Surface2Atlas^.surface:= nil;
+ Surface2Atlas^.shared:= false;
+ Surface2Atlas^.nextFrame:= nil;
+ Surface2Atlas^.cropInfo:= cropInfo;
+ exit;
+ end;
+
+ if (surf^.w <= 512) and (surf^.h <= 512) then
begin
Surface2Atlas:= Surface2Tex_(surf, enableClamp); // run the atlas side by side for debugging
+ Surface2Atlas^.cropInfo:= cropInfo;
ResetVertexArrays(Surface2Atlas);
exit;
end;
new(Surface2Atlas);
-Surface2Atlas^.PrevTexture:= nil;
-Surface2Atlas^.NextTexture:= nil;
-if TextureList <> nil then
- begin
- TextureList^.PrevTexture:= Surface2Atlas;
- Surface2Atlas^.NextTexture:= TextureList
- end;
-TextureList:= Surface2Atlas;
// Atlas allocation happens here later on. For now we just allocate one exclusive atlas per sprite
new(Surface2Atlas^.atlas);
@@ -206,13 +422,15 @@
Surface2Atlas^.isRotated:=false;
Surface2Atlas^.surface:= surf;
Surface2Atlas^.shared:= false;
+Surface2Atlas^.nextFrame:= nil;
+Surface2Atlas^.cropInfo:= cropInfo;
if (surf^.format^.BytesPerPixel <> 4) then
begin
TryDo(false, 'Surface2Tex failed, expecting 32 bit surface', true);
Surface2Atlas^.atlas^.id:= 0;
- exit
+ exit;
end;
@@ -281,26 +499,27 @@
// if nil is passed nothing is done
procedure FreeTexture(tex: PTexture);
begin
-if tex <> nil then
+ if tex <> nil then
begin
+ FreeTexture(tex^.nextFrame); // free all frames linked to this animation
+
+ if tex^.surface = nil then
+ begin
+ Dispose(tex);
+ exit;
+ end;
+
if tex^.shared then
begin
+ SDL_FreeSurface(tex^.surface);
FreeTexture_(tex); // run atlas side by side for debugging
- SDL_FreeSurface(tex^.surface);
exit;
end;
// Atlas cleanup happens here later on. For now we just free as each sprite has one atlas
+ glDeleteTextures(1, @tex^.atlas^.id);
Dispose(tex^.atlas);
- if tex^.NextTexture <> nil then
- tex^.NextTexture^.PrevTexture:= tex^.PrevTexture;
- if tex^.PrevTexture <> nil then
- tex^.PrevTexture^.NextTexture:= tex^.NextTexture
- else
- TextureList:= tex^.NextTexture;
- glDeleteTextures(1, @tex^.atlas^.id);
-
if (tex^.surface <> nil) then
SDL_FreeSurface(tex^.surface);
Dispose(tex);
@@ -309,19 +528,14 @@
procedure initModule;
begin
+assign(logFile, 'out.log');
+rewrite(logFile);
uAtlas.initModule;
-TextureList:= nil;
end;
procedure freeModule;
begin
-if TextureList <> nil then
- WriteToConsole('FIXME FIXME FIXME. App shutdown without full cleanup of texture list; read game0.log and please report this problem');
- while TextureList <> nil do
- begin
- AddFileLog('Sprite not freed: width='+inttostr(LongInt(TextureList^.w))+' height='+inttostr(LongInt(TextureList^.h))+' priority='+inttostr(round(TextureList^.atlas^.priority*1000)));
- FreeTexture(TextureList);
- end
+close(logFile);
end;
end.