uselectionhighlight.pas 10.0 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341
  1. // SPDX-License-Identifier: GPL-3.0-only
  2. unit USelectionHighlight;
  3. {$mode objfpc}{$H+}
  4. interface
  5. uses
  6. Classes, SysUtils, BGRABitmap, BGRABitmapTypes, UImage;
  7. const
  8. GridSize = 128;
  9. type
  10. { TSelectionHighlight }
  11. TSelectionHighlight = class
  12. protected
  13. FGrid: array of array of record
  14. selectionPoly: array of TPointF;
  15. selectionPolyComputed: boolean;
  16. end;
  17. FGridRows,FGridCols: integer;
  18. FFillSelection: boolean;
  19. FZoom: TPointF;
  20. FImage: TLazPaintImage;
  21. procedure SetFillSelection(AValue: boolean);
  22. procedure ComputeGridCells(const APixelRect: TRect);
  23. procedure DiscardGridCells(const APixelRect: TRect);
  24. function GetGridRect(const APixelRect: TRect): TRect;
  25. procedure ComputeGridCell(x,y: integer);
  26. public
  27. constructor Create(AImage: TLazPaintImage);
  28. destructor Destroy; override;
  29. procedure Update(ARenderWidth, ARenderHeight: Integer; {%H-}ARenderVisibleBounds: TRect);
  30. procedure Discard;
  31. procedure DrawAffine(ADestination: TBGRACustomBitmap; const AMatrix: TAffineMatrix; AFilter: TResampleFilter; AMode: TDrawMode = dmDrawWithTransparency; AOpacity: byte = 255);
  32. procedure NotifyChange(const ARect: TRect);
  33. property FillSelection: boolean read FFillSelection write SetFillSelection;
  34. end;
  35. implementation
  36. uses ugraph, Types, BGRATransform, BGRAVectorize, BGRAGradientScanner;
  37. { TSelectionHighlight }
  38. procedure TSelectionHighlight.SetFillSelection(AValue: boolean);
  39. begin
  40. if FFillSelection=AValue then Exit;
  41. FFillSelection:=AValue;
  42. Discard;
  43. end;
  44. procedure TSelectionHighlight.ComputeGridCells(const APixelRect: TRect);
  45. var
  46. y, x: integer;
  47. gr: TRect;
  48. begin
  49. gr := GetGridRect(APixelRect);
  50. for y := gr.Top to gr.Bottom-1 do
  51. for x := gr.Left to gr.Right-1 do
  52. ComputeGridCell(x,y);
  53. end;
  54. procedure TSelectionHighlight.DiscardGridCells(const APixelRect: TRect);
  55. var
  56. y, x: integer;
  57. gr: TRect;
  58. begin
  59. gr := GetGridRect(APixelRect);
  60. for y := gr.Top to gr.Bottom-1 do
  61. for x := gr.Left to gr.Right-1 do
  62. with FGrid[y,x] do
  63. begin
  64. selectionPoly := nil;
  65. selectionPolyComputed:= false;
  66. end;
  67. end;
  68. function TSelectionHighlight.GetGridRect(const APixelRect: TRect): TRect;
  69. begin
  70. result := rect(APixelRect.Left div GridSize, APixelRect.Top div GridSize,
  71. (APixelRect.Right+GridSize-1) div GridSize, (APixelRect.Bottom+GridSize-1) div GridSize);
  72. IntersectRect(result, result, rect(0,0,FGridCols,FGridRows));
  73. end;
  74. procedure TSelectionHighlight.ComputeGridCell(x, y: integer);
  75. const vectorizeMargin = 2;
  76. begin
  77. with FGrid[y,x] do
  78. begin
  79. if not selectionPolyComputed then
  80. begin
  81. selectionPoly := VectorizeMonochrome(FImage.SelectionMaskReadonly,
  82. rect(x*GridSize-vectorizeMargin,y*Gridsize-vectorizeMargin,
  83. (x+1)*GridSize+vectorizeMargin,(y+1)*GridSize+vectorizeMargin), 1,false,false);
  84. selectionPolyComputed:= true;
  85. end;
  86. end;
  87. end;
  88. constructor TSelectionHighlight.Create(AImage: TLazPaintImage);
  89. begin
  90. FFillSelection:= true;
  91. FImage := AImage;
  92. FZoom := PointF(1,1);
  93. FGridRows := 0;
  94. FGridCols := 0;
  95. end;
  96. destructor TSelectionHighlight.Destroy;
  97. begin
  98. inherited Destroy;
  99. end;
  100. procedure TSelectionHighlight.Update(ARenderWidth, ARenderHeight: Integer; ARenderVisibleBounds: TRect);
  101. begin
  102. if FImage.SelectionMaskEmpty then
  103. begin
  104. FGrid := nil;
  105. FGridRows := 0;
  106. FGridCols := 0;
  107. end
  108. else
  109. begin
  110. FGridRows := (FImage.Height+GridSize-1) div GridSize;
  111. FGridCols := (FImage.Width+GridSize-1) div GridSize;
  112. setlength(FGrid, FGridRows, FGridCols);
  113. end;
  114. FZoom := PointF(ARenderWidth/FImage.Width,ARenderHeight/FImage.Height);
  115. end;
  116. procedure TSelectionHighlight.Discard;
  117. var
  118. y, x: Integer;
  119. begin
  120. for y := 0 to FGridRows-1 do
  121. for x := 0 to FGridCols-1 do
  122. FGrid[y,x].selectionPolyComputed:= false;
  123. end;
  124. procedure TSelectionHighlight.DrawAffine(ADestination: TBGRACustomBitmap;
  125. const AMatrix: TAffineMatrix; AFilter: TResampleFilter; AMode: TDrawMode; AOpacity: byte);
  126. procedure DrawPoly(const AMatrix: TAffineMatrix; const APoly: array of TPointF;
  127. const ABounds: TRectF);
  128. const
  129. CoordShift = 8;
  130. CoordPrecision = 1 shl CoordShift;
  131. var
  132. m: TAffineMatrix;
  133. pts: array of record
  134. coord: TPoint;
  135. insideX,insideY: integer;
  136. end;
  137. function CoordShr(AValue: integer): integer; inline;
  138. begin
  139. if AValue >= 0 then
  140. result := AValue shr CoordShift
  141. else result := -((-AValue + CoordPrecision-1) shr CoordShift);
  142. end;
  143. procedure DrawSeg(AFrom,ATo: integer);
  144. var
  145. len, lenX,lenY, dotProd, value, value2: integer;
  146. ptFrom,ptTo: TPoint;
  147. u: TPointF;
  148. t: single;
  149. begin
  150. if (abs(pts[ATo].insideX+pts[AFrom].insideX)=2) or
  151. (abs(pts[ATo].insideY+pts[AFrom].insideY)=2) then exit;
  152. ptFrom := pts[AFrom].coord;
  153. ptTo := pts[ATo].coord;
  154. if (pts[ATo].insideX <> 0) or (pts[AFrom].insideX <> 0) or
  155. (pts[ATo].insideY <> 0) or (pts[AFrom].insideY <> 0) then
  156. begin
  157. u := APoly[ATo]-APoly[AFrom];
  158. if u.x <> 0 then
  159. begin
  160. if pts[ATo].insideX > 0 then
  161. begin
  162. t := (ABounds.Right-APoly[AFrom].x)/u.x;
  163. ptTo := (m*(APoly[AFrom]+u*t)).Round;
  164. end
  165. else if pts[ATo].insideX < 0 then
  166. begin
  167. t := (ABounds.Left-APoly[AFrom].x)/u.x;
  168. ptTo := (m*(APoly[AFrom]+u*t)).Round;
  169. end;
  170. if pts[AFrom].insideX > 0 then
  171. begin
  172. t := (ABounds.Right-APoly[ATo].x)/u.x;
  173. ptFrom := (m*(APoly[ATo]+u*t)).Round;
  174. end
  175. else if pts[AFrom].insideX < 0 then
  176. begin
  177. t := (ABounds.Left-APoly[ATo].x)/u.x;
  178. ptFrom := (m*(APoly[ATo]+u*t)).Round;
  179. end;
  180. end;
  181. if u.y <> 0 then
  182. begin
  183. if pts[ATo].insideY > 0 then
  184. begin
  185. t := (ABounds.Bottom-APoly[AFrom].y)/u.y;
  186. ptTo := (m*(APoly[AFrom]+u*t)).Round;
  187. end
  188. else if pts[ATo].insideY < 0 then
  189. begin
  190. t := (ABounds.Top-APoly[AFrom].y)/u.y;
  191. ptTo := (m*(APoly[AFrom]+u*t)).Round;
  192. end;
  193. if pts[AFrom].insideY > 0 then
  194. begin
  195. t := (ABounds.Bottom-APoly[ATo].y)/u.y;
  196. ptFrom := (m*(APoly[ATo]+u*t)).Round;
  197. end
  198. else if pts[AFrom].insideY < 0 then
  199. begin
  200. t := (ABounds.Top-APoly[ATo].y)/u.y;
  201. ptFrom := (m*(APoly[ATo]+u*t)).Round;
  202. end;
  203. end;
  204. end;
  205. lenX := abs(ptTo.x-ptFrom.x);
  206. lenY := abs(ptTo.y-ptFrom.y);
  207. len := lenX+lenY;
  208. if len = 0 then exit;
  209. dotProd := -(ptTo.x-ptFrom.x)+
  210. (ptTo.y-ptFrom.y);
  211. value := dotProd*127 div len + 128;
  212. value2 := (value*2+128) div 3;
  213. ptFrom.x := CoordShr(ptFrom.x);
  214. ptFrom.y := CoordShr(ptFrom.y);
  215. ptTo.x := CoordShr(ptTo.x);
  216. ptTo.y := CoordShr(ptTo.y);
  217. if value <= 128 then
  218. begin
  219. if lenY < lenX then
  220. ADestination.DrawLineAntialias(ptFrom.x,ptFrom.y-1,
  221. ptTo.x,ptTo.y-1, BGRA(value2,value2,value2), true) else
  222. ADestination.DrawLineAntialias(ptFrom.x-1,ptFrom.y,
  223. ptTo.x-1,ptTo.y, BGRA(value2,value2,value2), true);
  224. end else
  225. begin
  226. if lenY < lenX then
  227. ADestination.DrawLineAntialias(ptFrom.x,ptFrom.y-1,
  228. ptTo.x,ptTo.y-1, BGRA(value2,value2,value2), true) else
  229. ADestination.DrawLineAntialias(ptFrom.x-1,ptFrom.y,
  230. ptTo.x-1,ptTo.y, BGRA(value2,value2,value2), true);
  231. end;
  232. ADestination.DrawLineAntialias(ptFrom.x,ptFrom.y,
  233. ptTo.x,ptTo.y, BGRA(value,value,value), false);
  234. end;
  235. var
  236. i, j: Integer;
  237. begin
  238. m := AffineMatrixTranslation(CoordPrecision div 2, CoordPrecision div 2)*
  239. AffineMatrixScale(CoordPrecision,CoordPrecision)*AMatrix;
  240. pts := nil;
  241. setlength(pts, length(APoly));
  242. for i := 0 to high(APoly) do
  243. begin
  244. pts[i].coord := (m*APoly[i]).Round;
  245. if APoly[i].x <= ABounds.Left then pts[i].insideX := -1
  246. else if APoly[i].x >= ABounds.Right then pts[i].insideX := +1
  247. else pts[i].insideX:= 0;
  248. if APoly[i].y <= ABounds.Top then pts[i].insideY := -1
  249. else if APoly[i].y >= ABounds.Bottom then pts[i].insideY := +1
  250. else pts[i].insideY:= 0;
  251. end;
  252. j := 0;
  253. for i := 0 to high(APoly) do
  254. if IsEmptyPoint(pts[i].coord) then
  255. begin
  256. if i > j then DrawSeg(i-1,j);
  257. j := i+1;
  258. end else
  259. begin
  260. if i > j then DrawSeg(i-1,i);
  261. end;
  262. if length(APoly) > j then DrawSeg(high(APoly),j);
  263. end;
  264. var
  265. m: TAffineMatrix;
  266. aff: TBGRAAffineBitmapTransform;
  267. x,y: integer;
  268. b: TRectF;
  269. transf: TRect;
  270. band: TBGRABitmap;
  271. begin
  272. if FImage.SelectionMaskEmpty then exit;
  273. m := AMatrix*AffineMatrixScale(FZoom.x,FZoom.y);
  274. if FillSelection then
  275. begin
  276. aff := TBGRAAffineBitmapTransform.Create(FImage.SelectionMaskReadonly,false,AFilter);
  277. aff.ViewMatrix := AffineMatrixTranslation(-0.5,-0.5)*m*AffineMatrixTranslation(0.5,0.5);
  278. band := TBGRABitmap.Create(ADestination.ClipRect.Width,1);
  279. x := ADestination.ClipRect.Left;
  280. for y := ADestination.ClipRect.Top to ADestination.ClipRect.Bottom-1 do
  281. begin
  282. band.FillRect(0,0,band.Width,1, aff, dmSet, Point(x,y));
  283. ADestination.FillMask(x,y,band,BGRA(64,128,192,AOpacity div 2),AMode);
  284. end;
  285. band.Free;
  286. aff.Free;
  287. end;
  288. for y := 0 to FGridRows-1 do
  289. for x := 0 to FGridCols-1 do
  290. begin
  291. b := rectF(x*GridSize,y*GridSize,(x+1)*GridSize,(y+1)*GridSize);
  292. transf := (m*TAffineBox.AffineBox(b)).RectBounds;
  293. if transf.IntersectsWith(ADestination.ClipRect) then
  294. begin
  295. ComputeGridCell(x,y);
  296. if x = 0 then b.Left -= 1;
  297. if y = 0 then b.Top -= 1;
  298. if x = FGridCols-1 then b.right += 1;
  299. if y = FGridRows-1 then b.Bottom += 1;
  300. DrawPoly(m, FGrid[y,x].selectionPoly, b);
  301. end;
  302. end;
  303. end;
  304. procedure TSelectionHighlight.NotifyChange(const ARect: TRect);
  305. begin
  306. DiscardGridCells(ARect);
  307. end;
  308. end.