hedgewars/uTextures.pas
changeset 7304 8b3575750cd2
parent 7297 af64b509725c
child 7377 1aceade403ba
--- 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.