hedgewars/uRenderUtils.pas
changeset 4380 b78638b36b89
child 4403 0dfe26f48ec1
equal deleted inserted replaced
4379:6cd6b77df8b8 4380:b78638b36b89
       
     1 {$INCLUDE "options.inc"}
       
     2 unit uRenderUtils;
       
     3 
       
     4 interface
       
     5 uses SDLh, uTypes;
       
     6 
       
     7 procedure flipSurface(Surface: PSDL_Surface; Vertical: Boolean);
       
     8 procedure copyRotatedSurface(src, dest: PSDL_Surface); // this is necessary since width/height are read only in SDL
       
     9 procedure copyToXY(src, dest: PSDL_Surface; destX, destY: LongInt);
       
    10 function  RenderStringTex(s: ansistring; Color: Longword; font: THWFont): PTexture;
       
    11 function  RenderSpeechBubbleTex(s: ansistring; SpeechType: Longword; font: THWFont): PTexture;
       
    12 procedure DrawRoundRect(rect: PSDL_Rect; BorderColor, FillColor: Longword; Surface: PSDL_Surface; Clear: boolean);
       
    13 
       
    14 implementation
       
    15 uses uIO, uUtils, uVariables, uConsts, uTextures, sysutils;
       
    16 
       
    17 procedure DrawRoundRect(rect: PSDL_Rect; BorderColor, FillColor: Longword; Surface: PSDL_Surface; Clear: boolean);
       
    18 var r: TSDL_Rect;
       
    19 begin
       
    20     r:= rect^;
       
    21     if Clear then SDL_FillRect(Surface, @r, 0);
       
    22 
       
    23     BorderColor:= SDL_MapRGB(Surface^.format, BorderColor shr 16, BorderColor shr 8, BorderColor and $FF);
       
    24     FillColor:= SDL_MapRGB(Surface^.format, FillColor shr 16, FillColor shr 8, FillColor and $FF);
       
    25 
       
    26     r.y:= rect^.y + 1;
       
    27     r.h:= rect^.h - 2;
       
    28     SDL_FillRect(Surface, @r, BorderColor);
       
    29     r.x:= rect^.x + 1;
       
    30     r.w:= rect^.w - 2;
       
    31     r.y:= rect^.y;
       
    32     r.h:= rect^.h;
       
    33     SDL_FillRect(Surface, @r, BorderColor);
       
    34     r.x:= rect^.x + 2;
       
    35     r.y:= rect^.y + 1;
       
    36     r.w:= rect^.w - 4;
       
    37     r.h:= rect^.h - 2;
       
    38     SDL_FillRect(Surface, @r, FillColor);
       
    39     r.x:= rect^.x + 1;
       
    40     r.y:= rect^.y + 2;
       
    41     r.w:= rect^.w - 2;
       
    42     r.h:= rect^.h - 4;
       
    43     SDL_FillRect(Surface, @r, FillColor)
       
    44 end;
       
    45 
       
    46 function WriteInRoundRect(Surface: PSDL_Surface; X, Y: LongInt; Color: LongWord; Font: THWFont; s: ansistring): TSDL_Rect;
       
    47 var w, h: LongInt;
       
    48     tmpsurf: PSDL_Surface;
       
    49     clr: TSDL_Color;
       
    50     finalRect: TSDL_Rect;
       
    51 begin
       
    52     TTF_SizeUTF8(Fontz[Font].Handle, Str2PChar(s), w, h);
       
    53     finalRect.x:= X;
       
    54     finalRect.y:= Y;
       
    55     finalRect.w:= w + FontBorder * 2 + 4;
       
    56     finalRect.h:= h + FontBorder * 2;
       
    57     DrawRoundRect(@finalRect, cWhiteColor, endian(cNearBlackColorChannels.value), Surface, true);
       
    58     clr.r:= (Color shr 16) and $FF;
       
    59     clr.g:= (Color shr 8) and $FF;
       
    60     clr.b:= Color and $FF;
       
    61     tmpsurf:= TTF_RenderUTF8_Blended(Fontz[Font].Handle, Str2PChar(s), clr);
       
    62     finalRect.x:= X + FontBorder + 2;
       
    63     finalRect.y:= Y + FontBorder;
       
    64     SDLTry(tmpsurf <> nil, true);
       
    65     SDL_UpperBlit(tmpsurf, nil, Surface, @finalRect);
       
    66     SDL_FreeSurface(tmpsurf);
       
    67     finalRect.x:= X;
       
    68     finalRect.y:= Y;
       
    69     finalRect.w:= w + FontBorder * 2 + 4;
       
    70     finalRect.h:= h + FontBorder * 2;
       
    71     WriteInRoundRect:= finalRect;
       
    72 end;
       
    73 
       
    74 procedure flipSurface(Surface: PSDL_Surface; Vertical: Boolean);
       
    75 var y, x, i, j: LongInt;
       
    76     tmpPixel: Longword;
       
    77     pixels: PLongWordArray;
       
    78 begin
       
    79     TryDo(Surface^.format^.BytesPerPixel = 4, 'flipSurface failed, expecting 32 bit surface', true);
       
    80     pixels:= Surface^.pixels;
       
    81     if Vertical then
       
    82     for y := 0 to (Surface^.h div 2) - 1 do
       
    83         for x := 0 to Surface^.w - 1 do
       
    84             begin
       
    85             i:= y * Surface^.w + x;
       
    86             j:= (Surface^.h - y - 1) * Surface^.w + x;
       
    87             tmpPixel:= pixels^[i];
       
    88             pixels^[i]:= pixels^[j];
       
    89             pixels^[j]:= tmpPixel;
       
    90             end
       
    91     else
       
    92     for x := 0 to (Surface^.w div 2) - 1 do
       
    93         for y := 0 to Surface^.h -1 do
       
    94             begin
       
    95             i:= y*Surface^.w + x;
       
    96             j:= y*Surface^.w + (Surface^.w - x - 1);
       
    97             tmpPixel:= pixels^[i];
       
    98             pixels^[i]:= pixels^[j];
       
    99             pixels^[j]:= tmpPixel;
       
   100             end;
       
   101 end;
       
   102 
       
   103 procedure copyToXY(src, dest: PSDL_Surface; destX, destY: LongInt);
       
   104 var srcX, srcY, i, j, maxDest: LongInt;
       
   105     srcPixels, destPixels: PLongWordArray;
       
   106     r0, g0, b0, a0, r1, g1, b1, a1: Byte;
       
   107 begin
       
   108     maxDest:= (dest^.pitch div 4) * dest^.h;
       
   109     srcPixels:= src^.pixels;
       
   110     destPixels:= dest^.pixels;
       
   111 
       
   112     for srcX:= 0 to src^.w - 1 do
       
   113     for srcY:= 0 to src^.h - 1 do
       
   114         begin
       
   115         i:= (destY + srcY) * (dest^.pitch div 4) + destX + srcX;
       
   116         j:= srcY * (src^.pitch div 4) + srcX;
       
   117         if (i < maxDest) and (srcPixels^[j] and AMask <> 0) then
       
   118             begin
       
   119             SDL_GetRGBA(destPixels^[i], dest^.format, @r0, @g0, @b0, @a0);
       
   120             SDL_GetRGBA(srcPixels^[j], src^.format, @r1, @g1, @b1, @a1);
       
   121             r0:= (r0 * (255 - LongInt(a1)) + r1 * LongInt(a1)) div 255;
       
   122             g0:= (g0 * (255 - LongInt(a1)) + g1 * LongInt(a1)) div 255;
       
   123             b0:= (b0 * (255 - LongInt(a1)) + b1 * LongInt(a1)) div 255;
       
   124             a0:= (a0 * (255 - LongInt(a1)) + a1 * LongInt(a1)) div 255;
       
   125             destPixels^[i]:= SDL_MapRGBA(dest^.format, r0, g0, b0, a0);
       
   126             end;
       
   127         end;
       
   128 end;
       
   129 
       
   130 procedure copyRotatedSurface(src, dest: PSDL_Surface); // this is necessary since width/height are read only in SDL, apparently
       
   131 var y, x, i, j: LongInt;
       
   132     srcPixels, destPixels: PLongWordArray;
       
   133 begin
       
   134     TryDo(src^.format^.BytesPerPixel = 4, 'rotateSurface failed, expecting 32 bit surface', true);
       
   135     TryDo(dest^.format^.BytesPerPixel = 4, 'rotateSurface failed, expecting 32 bit surface', true);
       
   136 
       
   137     srcPixels:= src^.pixels;
       
   138     destPixels:= dest^.pixels;
       
   139 
       
   140     j:= 0;
       
   141     for x := 0 to src^.w - 1 do
       
   142         for y := 0 to src^.h - 1 do
       
   143             begin
       
   144             i:= (src^.h - 1 - y) * (src^.pitch div 4) + x;
       
   145             destPixels^[j]:= srcPixels^[i];
       
   146             inc(j)
       
   147             end;
       
   148 end;
       
   149 
       
   150 function  RenderStringTex(s: ansistring; Color: Longword; font: THWFont): PTexture;
       
   151 var w, h: LongInt;
       
   152     finalSurface: PSDL_Surface;
       
   153 begin
       
   154     if length(s) = 0 then s:= ' ';
       
   155     font:= CheckCJKFont(s, font);
       
   156     w:= 0; h:= 0; // avoid compiler hints
       
   157     TTF_SizeUTF8(Fontz[font].Handle, Str2PChar(s), w, h);
       
   158 
       
   159     finalSurface:= SDL_CreateRGBSurface(SDL_SWSURFACE, w + FontBorder * 2 + 4, h + FontBorder * 2,
       
   160             32, RMask, GMask, BMask, AMask);
       
   161 
       
   162     TryDo(finalSurface <> nil, 'RenderString: fail to create surface', true);
       
   163 
       
   164     WriteInRoundRect(finalSurface, 0, 0, Color, font, s);
       
   165 
       
   166     TryDo(SDL_SetColorKey(finalSurface, SDL_SRCCOLORKEY, 0) = 0, errmsgTransparentSet, true);
       
   167 
       
   168     RenderStringTex:= Surface2Tex(finalSurface, false);
       
   169 
       
   170     SDL_FreeSurface(finalSurface);
       
   171 end;
       
   172 
       
   173 
       
   174 function RenderSpeechBubbleTex(s: ansistring; SpeechType: Longword; font: THWFont): PTexture;
       
   175 var textWidth, textHeight, x, y, w, h, i, j, pos, prevpos, line, numLines, edgeWidth, edgeHeight, cornerWidth, cornerHeight: LongInt;
       
   176     finalSurface, tmpsurf, rotatedEdge: PSDL_Surface;
       
   177     rect: TSDL_Rect;
       
   178     chars: set of char = [#9,' ','.',';',':','?','!',','];
       
   179     substr: shortstring;
       
   180     edge, corner, tail: TSPrite;
       
   181 begin
       
   182     case SpeechType of
       
   183         1: begin;
       
   184         edge:= sprSpeechEdge;
       
   185         corner:= sprSpeechCorner;
       
   186         tail:= sprSpeechTail;
       
   187         end;
       
   188         2: begin;
       
   189         edge:= sprThoughtEdge;
       
   190         corner:= sprThoughtCorner;
       
   191         tail:= sprThoughtTail;
       
   192         end;
       
   193         3: begin;
       
   194         edge:= sprShoutEdge;
       
   195         corner:= sprShoutCorner;
       
   196         tail:= sprShoutTail;
       
   197         end;
       
   198         end;
       
   199     edgeHeight:= SpritesData[edge].Height;
       
   200     edgeWidth:= SpritesData[edge].Width;
       
   201     cornerWidth:= SpritesData[corner].Width;
       
   202     cornerHeight:= SpritesData[corner].Height;
       
   203     // This one screws up WrapText
       
   204     //s:= 'This is the song that never ends.  ''cause it goes on and on my friends. Some people, started singing it not knowing what it was. And they''ll just go on singing it forever just because... This is the song that never ends...';
       
   205     // This one does not
       
   206     //s:= 'This is the song that never ends.  cause it goes on and on my friends. Some people, started singing it not knowing what it was. And they will go on singing it forever just because... This is the song that never ends... ';
       
   207 
       
   208     numLines:= 0;
       
   209 
       
   210     if length(s) = 0 then s:= '...';
       
   211     font:= CheckCJKFont(s, font);
       
   212     w:= 0; h:= 0; // avoid compiler hints
       
   213     TTF_SizeUTF8(Fontz[font].Handle, Str2PChar(s), w, h);
       
   214     if w<8 then w:= 8;
       
   215     j:= 0;
       
   216     if (length(s) > 20) then
       
   217         begin
       
   218         w:= 0;
       
   219         i:= round(Sqrt(length(s)) * 2);
       
   220         s:= WrapText(s, #1, chars, i);
       
   221         pos:= 1; prevpos:= 0; line:= 0;
       
   222     // Find the longest line for the purposes of centring the text.  Font dependant.
       
   223         while pos <= length(s) do
       
   224             begin
       
   225             if (s[pos] = #1) or (pos = length(s)) then
       
   226                 begin
       
   227                 inc(numlines);
       
   228                 if s[pos] <> #1 then inc(pos);
       
   229                 while s[prevpos+1] = ' ' do inc(prevpos);
       
   230                 substr:= copy(s, prevpos+1, pos-prevpos-1);
       
   231                 i:= 0; j:= 0;
       
   232                 TTF_SizeUTF8(Fontz[font].Handle, Str2PChar(substr), i, j);
       
   233                 if i > w then w:= i;
       
   234                 prevpos:= pos;
       
   235                 end;
       
   236             inc(pos);
       
   237             end;
       
   238         end
       
   239     else numLines := 1;
       
   240 
       
   241     textWidth:=((w-(cornerWidth-edgeWidth)*2) div edgeWidth)*edgeWidth+edgeWidth;
       
   242     textHeight:=(((numlines * h + 2)-((cornerHeight-edgeWidth)*2)) div edgeWidth)*edgeWidth;
       
   243 
       
   244     textHeight:=max(textHeight,edgeWidth);
       
   245     //textWidth:=max(textWidth,SpritesData[tail].Width);
       
   246     rect.x:= 0;
       
   247     rect.y:= 0;
       
   248     rect.w:= textWidth + (cornerWidth * 2);
       
   249     rect.h:= textHeight + cornerHeight*2 - edgeHeight + SpritesData[tail].Height;
       
   250     //s:= inttostr(w) + ' ' + inttostr(numlines) + ' ' + inttostr(rect.x) + ' '+inttostr(rect.y) + ' ' + inttostr(rect.w) + ' ' + inttostr(rect.h);
       
   251 
       
   252     finalSurface:= SDL_CreateRGBSurface(SDL_SWSURFACE, rect.w, rect.h, 32, RMask, GMask, BMask, AMask);
       
   253 
       
   254     TryDo(finalSurface <> nil, 'RenderString: fail to create surface', true);
       
   255 
       
   256     //////////////////////////////// CORNERS ///////////////////////////////
       
   257     copyToXY(SpritesData[corner].Surface, finalSurface, 0, 0); /////////////////// NW
       
   258 
       
   259     flipSurface(SpritesData[corner].Surface, true); // store all 4 versions in memory to avoid repeated flips?
       
   260     x:= 0;
       
   261     y:= textHeight + cornerHeight -1;
       
   262     copyToXY(SpritesData[corner].Surface, finalSurface, x, y); /////////////////// SW
       
   263 
       
   264     flipSurface(SpritesData[corner].Surface, false);
       
   265     x:= rect.w-cornerWidth-1;
       
   266     y:= textHeight + cornerHeight -1;
       
   267     copyToXY(SpritesData[corner].Surface, finalSurface, x, y); /////////////////// SE
       
   268 
       
   269     flipSurface(SpritesData[corner].Surface, true);
       
   270     x:= rect.w-cornerWidth-1;
       
   271     y:= 0;
       
   272     copyToXY(SpritesData[corner].Surface, finalSurface, x, y); /////////////////// NE
       
   273     flipSurface(SpritesData[corner].Surface, false); // restore original position
       
   274     //////////////////////////////// END CORNERS ///////////////////////////////
       
   275 
       
   276     //////////////////////////////// EDGES //////////////////////////////////////
       
   277     x:= cornerWidth;
       
   278     y:= 0;
       
   279     while x < rect.w-cornerWidth-1 do
       
   280         begin
       
   281         copyToXY(SpritesData[edge].Surface, finalSurface, x, y); ///////////////// top edge
       
   282         inc(x,edgeWidth);
       
   283         end;
       
   284     flipSurface(SpritesData[edge].Surface, true);
       
   285     x:= cornerWidth;
       
   286     y:= textHeight + cornerHeight*2 - edgeHeight-1;
       
   287     while x < rect.w-cornerWidth-1 do
       
   288         begin
       
   289         copyToXY(SpritesData[edge].Surface, finalSurface, x, y); ///////////////// bottom edge
       
   290         inc(x,edgeWidth);
       
   291         end;
       
   292     flipSurface(SpritesData[edge].Surface, true); // restore original position
       
   293 
       
   294     rotatedEdge:= SDL_CreateRGBSurface(SDL_SWSURFACE, edgeHeight, edgeWidth, 32, RMask, GMask, BMask, AMask);
       
   295     x:= rect.w - edgeHeight - 1;
       
   296     y:= cornerHeight;
       
   297     //// initially was going to rotate in place, but the SDL spec claims width/height are read only
       
   298     copyRotatedSurface(SpritesData[edge].Surface,rotatedEdge);
       
   299     while y < textHeight + cornerHeight do
       
   300         begin
       
   301         copyToXY(rotatedEdge, finalSurface, x, y);
       
   302         inc(y,edgeWidth);
       
   303         end;
       
   304     flipSurface(rotatedEdge, false); // restore original position
       
   305     x:= 0;
       
   306     y:= cornerHeight;
       
   307     while y < textHeight + cornerHeight do
       
   308         begin
       
   309         copyToXY(rotatedEdge, finalSurface, x, y);
       
   310         inc(y,edgeWidth);
       
   311         end;
       
   312     //////////////////////////////// END EDGES //////////////////////////////////////
       
   313 
       
   314     x:= cornerWidth;
       
   315     y:= textHeight + cornerHeight * 2 - edgeHeight - 1;
       
   316     copyToXY(SpritesData[tail].Surface, finalSurface, x, y);
       
   317 
       
   318     rect.x:= edgeHeight;
       
   319     rect.y:= edgeHeight;
       
   320     rect.w:= rect.w - edgeHeight * 2;
       
   321     rect.h:= textHeight + cornerHeight * 2 - edgeHeight * 2;
       
   322     i:= rect.w;
       
   323     j:= rect.h;
       
   324     SDL_FillRect(finalSurface, @rect, cWhiteColor);
       
   325 
       
   326     pos:= 1; prevpos:= 0; line:= 0;
       
   327     while pos <= length(s) do
       
   328         begin
       
   329         if (s[pos] = #1) or (pos = length(s)) then
       
   330             begin
       
   331             if s[pos] <> #1 then inc(pos);
       
   332             while s[prevpos+1] = ' 'do inc(prevpos);
       
   333             substr:= copy(s, prevpos+1, pos-prevpos-1);
       
   334             if Length(substr) <> 0 then
       
   335             begin
       
   336             tmpsurf:= TTF_RenderUTF8_Blended(Fontz[Font].Handle, Str2PChar(substr), cNearBlackColorChannels);
       
   337             rect.x:= edgeHeight + 1 + ((i - w) div 2);
       
   338             // trying to more evenly position the text, vertically
       
   339             rect.y:= edgeHeight + ((j-(numLines*h)) div 2) + line * h;
       
   340             SDLTry(tmpsurf <> nil, true);
       
   341             SDL_UpperBlit(tmpsurf, nil, finalSurface, @rect);
       
   342             SDL_FreeSurface(tmpsurf);
       
   343             inc(line);
       
   344             prevpos:= pos;
       
   345             end;
       
   346             end;
       
   347         inc(pos);
       
   348         end;
       
   349 
       
   350     RenderSpeechBubbleTex:= Surface2Tex(finalSurface, true);
       
   351 
       
   352     SDL_FreeSurface(rotatedEdge);
       
   353     SDL_FreeSurface(finalSurface);
       
   354 end;
       
   355 
       
   356 end.