Вы не можете выбрать более 25 тем Темы должны начинаться с буквы или цифры, могут содержать дефисы(-) и должны содержать не более 35 символов.

438 строки
13 KiB

  1. unit utsImage;
  2. {$IFDEF FPC}
  3. {$mode objfpc}{$H+}
  4. {$ENDIF}
  5. interface
  6. uses
  7. Classes, SysUtils,
  8. utsTypes, utsUtils;
  9. type
  10. ////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
  11. TtsImage = class;
  12. TtsImageFunc = procedure(const aImage: TtsImage; X, Y: Integer; var aPixel: TtsColor4f; aArgs: Pointer);
  13. TtsImage = class(TObject)
  14. private
  15. fWidth: Integer;
  16. fHeight: Integer;
  17. fDataSize: Integer;
  18. fLineSize: Integer;
  19. fFormat: TtsFormat;
  20. fData: Pointer;
  21. fHasScanlines: Boolean;
  22. fScanlines: array of Pointer;
  23. function GetScanline(const aIndex: Integer): Pointer;
  24. function GetIsEmpty: Boolean;
  25. procedure UpdateScanlines;
  26. procedure SetData(
  27. const aData: Pointer;
  28. const aFormat: TtsFormat = tsFormatEmpty;
  29. const aWidth: Integer = 0;
  30. const aHeight: Integer = 0;
  31. const aLineSize: Integer = 0;
  32. const aDataSize: Integer = 0);
  33. public
  34. property IsEmpty: Boolean read GetIsEmpty;
  35. property Width: Integer read fWidth;
  36. property Height: Integer read fHeight;
  37. property LineSize: Integer read fLineSize;
  38. property DataSize: Integer read fDataSize;
  39. property Format: TtsFormat read fFormat;
  40. property Data: Pointer read fData;
  41. property Scanline[const aIndex: Integer]: Pointer read GetScanline;
  42. function GetPixelAt(const x, y: Integer; out aColor: TtsColor4f): Boolean;
  43. procedure Assign(const aImage: TtsImage);
  44. procedure CreateEmpty(const aFormat: TtsFormat; const aWidth, aHeight: Integer);
  45. procedure LoadFromFunc(const aFunc: TtsImageFunc; const aArgs: Pointer);
  46. procedure Resize(const aNewWidth, aNewHeight, X, Y: Integer);
  47. procedure FindMinMax(out aRect: TtsRect);
  48. procedure FillColor(const aColor: TtsColor4f; const aChannelMask: TtsColorChannels; const aModes: TtsImageModes);
  49. procedure FillPattern(const aPattern: TtsImage; X, Y: Integer; const aChannelMask: TtsColorChannels; const aModes: TtsImageModes);
  50. procedure Blend(const aImage: TtsImage; const X, Y: Integer; const aFunc: TtsBlendColorFunc);
  51. procedure Blur(const aHorzKernel, aVertKernel: TtsKernel1D; const aChannelMask: TtsColorChannels);
  52. constructor Create;
  53. destructor Destroy; override;
  54. end;
  55. implementation
  56. uses
  57. Math,
  58. utsConstants;
  59. ////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
  60. //TtsImage//////////////////////////////////////////////////////////////////////////////////////////////////////////////
  61. ////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
  62. function TtsImage.GetScanline(const aIndex: Integer): Pointer;
  63. begin
  64. if not fHasScanlines then
  65. UpdateScanlines;
  66. if fHasScanlines and (aIndex >= 0) and (aIndex <= High(fScanlines)) then
  67. result := fScanlines[aIndex]
  68. else
  69. result := nil;
  70. end;
  71. ////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
  72. function TtsImage.GetIsEmpty: Boolean;
  73. begin
  74. result := not Assigned(fData);
  75. end;
  76. ////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
  77. procedure TtsImage.UpdateScanlines;
  78. var
  79. i: Integer;
  80. tmp: PByte;
  81. begin
  82. SetLength(fScanlines, fHeight);
  83. for i := 0 to fHeight-1 do begin
  84. tmp := fData;
  85. inc(tmp, i * fLineSize);
  86. fScanlines[i] := tmp;
  87. end;
  88. fHasScanlines := true;
  89. end;
  90. ////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
  91. procedure TtsImage.SetData(const aData: Pointer; const aFormat: TtsFormat;
  92. const aWidth: Integer; const aHeight: Integer;
  93. const aLineSize: Integer; const aDataSize: Integer);
  94. begin
  95. fHasScanlines := false;
  96. if Assigned(fData) then
  97. FreeMemory(fData);
  98. fData := aData;
  99. if Assigned(fData) then begin
  100. fWidth := aWidth;
  101. fHeight := aHeight;
  102. fFormat := aFormat;
  103. fLineSize := aLineSize;
  104. fDataSize := aDataSize;
  105. end else begin
  106. fWidth := 0;
  107. fHeight := 0;
  108. fLineSize := 0;
  109. fDataSize := 0;
  110. fFormat := tsFormatEmpty;
  111. end;
  112. end;
  113. ////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
  114. function TtsImage.GetPixelAt(const x, y: Integer; out aColor: TtsColor4f): Boolean;
  115. var
  116. p: PByte;
  117. begin
  118. result := (x >= 0) and (x < Width) and (y >= 0) and (y < Height);
  119. if result then begin
  120. p := Scanline[y];
  121. inc(p, x * tsFormatSize(Format));
  122. tsFormatUnmap(Format, p, aColor);
  123. end;
  124. end;
  125. ////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
  126. procedure TtsImage.Assign(const aImage: TtsImage);
  127. var
  128. ImgData: Pointer;
  129. begin
  130. GetMem(ImgData, aImage.DataSize);
  131. if Assigned(ImgData) then
  132. Move(aImage.Data^, ImgData^, aImage.DataSize);
  133. SetData(ImgData, aImage.Format, aImage.Width, aImage.Height, aImage.LineSize, aImage.DataSize);
  134. end;
  135. ////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
  136. procedure TtsImage.CreateEmpty(const aFormat: TtsFormat; const aWidth, aHeight: Integer);
  137. var
  138. ImgData: PByte;
  139. lSize, dSize: Integer;
  140. begin
  141. lSize := aWidth * tsFormatSize(aFormat);
  142. lSize := lSize + ((4 - (lSize mod 4)) mod 4);
  143. dSize := aHeight * lSize;
  144. ImgData := AllocMem(dSize);
  145. FillChar(ImgData^, dSize, #0);
  146. SetData(ImgData, aFormat, aWidth, aHeight, lSize, dSize);
  147. end;
  148. ////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
  149. procedure TtsImage.LoadFromFunc(const aFunc: TtsImageFunc; const aArgs: Pointer);
  150. var
  151. X, Y: Integer;
  152. c: TtsColor4f;
  153. p, tmp: PByte;
  154. begin
  155. for Y := 0 to Height - 1 do begin
  156. p := ScanLine[Y];
  157. for X := 0 to Width - 1 do begin
  158. tmp := p;
  159. tsFormatUnmap(fFormat, tmp, c);
  160. aFunc(Self, X, Y, c, aArgs);
  161. tsFormatMap(fFormat, p, c);
  162. end;
  163. end;
  164. end;
  165. ////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
  166. procedure TtsImage.Resize(const aNewWidth, aNewHeight, X, Y: Integer);
  167. var
  168. ImgData: PByte;
  169. pSize, lSize, dSize: Integer;
  170. src, dst: PByte;
  171. YStart, YEnd, YPos, XStart, XEnd: Integer;
  172. begin
  173. if (aNewHeight = 0) or (aNewWidth = 0) then begin
  174. SetData(nil);
  175. exit;
  176. end;
  177. pSize := tsFormatSize(Format);
  178. lSize := pSize * aNewWidth;
  179. lSize := lSize + ((4 - (lSize mod 4)) mod 4);
  180. dSize := lSize * aNewHeight;
  181. GetMem(ImgData, dSize);
  182. try
  183. FillChar(ImgData^, dSize, 0);
  184. // positions
  185. YStart := Max(0, Y);
  186. YEnd := Min(aNewHeight, Y + Height);
  187. XStart := Max(0, X);
  188. XEnd := Min(aNewWidth, X + Width);
  189. // copy data
  190. for YPos := YStart to YEnd -1 do begin
  191. dst := ImgData;
  192. Inc(dst, lSize * YPos + pSize * XStart);
  193. src := fData;
  194. Inc(src, fLineSize * (YPos - Y) + pSize * (XStart - X));
  195. Move(src^, dst^, (XEnd - XStart) * pSize);
  196. end;
  197. // assign
  198. SetData(ImgData, Format, aNewWidth, aNewHeight, lSize, dSize);
  199. except
  200. FreeMem(ImgData);
  201. end;
  202. end;
  203. ////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
  204. procedure TtsImage.FindMinMax(out aRect: TtsRect);
  205. var
  206. X, Y: Integer;
  207. c: TtsColor4f;
  208. p: PByte;
  209. begin
  210. aRect.Top := -1;
  211. aRect.Left := -1;
  212. aRect.Right := -1;
  213. aRect.Bottom := -1;
  214. // Search for MinMax
  215. for Y := 0 to Height-1 do begin
  216. p := ScanLine[Y];
  217. for X := 0 to Width-1 do begin
  218. tsFormatUnmap(Format, p, c);
  219. if c.a > 0 then begin
  220. if (X < aRect.Left) or (aRect.Left = -1) then
  221. aRect.Left := X;
  222. if (X+1 > aRect.Right) or (aRect.Right = -1) then
  223. aRect.Right := X+1;
  224. if (Y < aRect.Top) or (aRect.Top = -1) then
  225. aRect.Top := Y;
  226. if (Y+1 > aRect.Bottom) or (aRect.Bottom = -1) then
  227. aRect.Bottom := Y+1;
  228. end;
  229. end;
  230. end;
  231. end;
  232. ////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
  233. procedure TtsImage.FillColor(const aColor: TtsColor4f; const aChannelMask: TtsColorChannels;
  234. const aModes: TtsImageModes);
  235. var
  236. x, y: Integer;
  237. rp, wp: PByte;
  238. c: TtsColor4f;
  239. ch: TtsColorChannel;
  240. i: Integer;
  241. begin
  242. for y := 0 to Height-1 do begin
  243. rp := Scanline[y];
  244. wp := rp;
  245. for x := 0 to Width-1 do begin
  246. tsFormatUnmap(Format, rp, c);
  247. for i := 0 to 3 do begin
  248. ch := TtsColorChannel(i);
  249. if (ch in aChannelMask) then
  250. c.arr[i] := TS_IMAGE_MODE_FUNCTIONS[aModes[ch]](aColor.arr[i], c.arr[i]);
  251. end;
  252. tsFormatMap(Format, wp, c);
  253. end;
  254. end;
  255. end;
  256. ////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
  257. procedure TtsImage.FillPattern(const aPattern: TtsImage; X, Y: Integer;
  258. const aChannelMask: TtsColorChannels; const aModes: TtsImageModes);
  259. var
  260. _x, _y, posX, i: Integer;
  261. src, dst, tmp: PByte;
  262. cSrc, cDst: TtsColor4f;
  263. ch: TtsColorChannel;
  264. begin
  265. if x < 0 then
  266. x := Random(aPattern.Width);
  267. if y < 0 then
  268. y := Random(aPattern.Height);
  269. for _y := 0 to Height-1 do begin
  270. src := aPattern.Scanline[(y + _y) mod aPattern.Height];
  271. dst := Scanline[_y];
  272. inc(src, x);
  273. posX := x;
  274. for _x := 0 to Width-1 do begin
  275. if (posX >= aPattern.Width) then begin
  276. src := aPattern.Scanline[(y + _y) mod aPattern.Height];
  277. posX := 0;
  278. end;
  279. tmp := dst;
  280. tsFormatUnmap(aPattern.Format, src, cSrc);
  281. tsFormatUnmap(Format, tmp, cDst);
  282. for i := 0 to 3 do begin
  283. ch := TtsColorChannel(i);
  284. if (ch in aChannelMask) then
  285. cDst.arr[i] := TS_IMAGE_MODE_FUNCTIONS[aModes[ch]](cSrc.arr[i], cDst.arr[i]);
  286. end;
  287. tsFormatMap(Format, dst, cDst);
  288. inc(posX);
  289. end;
  290. end;
  291. end;
  292. ////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
  293. procedure TtsImage.Blend(const aImage: TtsImage; const X, Y: Integer; const aFunc: TtsBlendColorFunc);
  294. var
  295. _x, _y, x1, x2, y1, y2: Integer;
  296. src, dst, tmp: PByte;
  297. srcColor, dstColor: TtsColor4f;
  298. srcPixelSize, dstPixelSize: Integer;
  299. begin
  300. x1 := Max(X, 0);
  301. x2 := Min(X + aImage.Width , Width);
  302. y1 := Max(Y, 0);
  303. y2 := Min(Y + aImage.Height, Height);
  304. srcPixelSize := tsFormatSize(aImage.Format);
  305. dstPixelSize := tsFormatSize(Format);
  306. for _y := y1 to y2-1 do begin
  307. src := aImage.Scanline[_y - min(y1, y)];
  308. dst := Scanline[_y];
  309. inc(src, (x1 - x) * srcPixelSize);
  310. inc(dst, x1 * dstPixelSize);
  311. tmp := dst;
  312. for _x := x1 to x2-1 do begin
  313. tsFormatUnmap(aImage.Format, src, srcColor);
  314. tsFormatUnmap( Format, dst, dstColor);
  315. tsFormatMap(aImage.Format, tmp, aFunc(srcColor, dstColor));
  316. end;
  317. end;
  318. end;
  319. ////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
  320. procedure TtsImage.Blur(const aHorzKernel, aVertKernel: TtsKernel1D; const aChannelMask: TtsColorChannels);
  321. var
  322. tmpImage: TtsImage;
  323. procedure DoBlur(const aSrc, aDst: TtsImage; const aKernel: TtsKernel1D; const ShiftX, ShiftY: Integer);
  324. var
  325. x, y, i, j: Integer;
  326. src, dst: PByte;
  327. v: Single;
  328. c, tmp: TtsColor4f;
  329. begin
  330. for y := 0 to Height-1 do begin
  331. src := aSrc.Scanline[y];
  332. dst := aDst.Scanline[y];
  333. for x := 0 to Width-1 do begin
  334. // read color and clear channels
  335. v := 0;
  336. tsFormatUnmap(aSrc.Format, src, c);
  337. for i := 0 to 3 do
  338. if (TtsColorChannel(i) in aChannelMask) then
  339. c.arr[i] := 0;
  340. // do blur
  341. for i := 0 to aKernel.ItemCount-1 do with aKernel.Items[i] do begin
  342. if aSrc.GetPixelAt(x + Offset * ShiftX, y + Offset * ShiftY, tmp) then begin
  343. for j := 0 to 3 do begin
  344. if (TtsColorChannel(j) in aChannelMask) then
  345. c.arr[j] := c.arr[j] + tmp.arr[j] * Value;
  346. end;
  347. v := v + Value;
  348. end;
  349. end;
  350. // calc final color and write
  351. for i := 0 to 3 do
  352. if (TtsColorChannel(i) in aChannelMask) then
  353. c.arr[i] := c.arr[i] / v;
  354. tsFormatMap(aDst.Format, dst, c);
  355. end;
  356. end;
  357. end;
  358. begin
  359. tmpImage := TtsImage.Create;
  360. try
  361. tmpImage.CreateEmpty(Format, Width, Height);
  362. tmpImage.FillColor(tsColor4f(1, 1, 1, 0), TS_COLOR_CHANNELS_RGBA, TS_IMAGE_MODES_REPLACE_ALL);
  363. DoBlur(self, tmpImage, aHorzKernel, 1, 0);
  364. DoBlur(tmpImage, self, aVertKernel, 0, 1);
  365. finally
  366. FreeAndNil(tmpImage);
  367. end;
  368. end;
  369. ////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
  370. constructor TtsImage.Create;
  371. begin
  372. inherited Create;
  373. SetData(nil);
  374. end;
  375. ////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
  376. destructor TtsImage.Destroy;
  377. begin
  378. SetData(nil);
  379. inherited Destroy;
  380. end;
  381. end.