Qt
Internal/Contributor docs for the Qt SDK. Note: These are NOT official API docs; those are found at https://doc.qt.io/
Loading...
Searching...
No Matches
cstretchengine.cpp
Go to the documentation of this file.
1// Copyright 2017 The PDFium Authors
2// Use of this source code is governed by a BSD-style license that can be
3// found in the LICENSE file.
4
5// Original code copyright 2014 Foxit Software Inc. http://www.foxitsoftware.com
6
7#include "core/fxge/dib/cstretchengine.h"
8
9#include <math.h>
10
11#include <algorithm>
12#include <type_traits>
13#include <utility>
14
15#include "core/fxcrt/check.h"
16#include "core/fxcrt/fx_safe_types.h"
17#include "core/fxcrt/fx_system.h"
18#include "core/fxcrt/pauseindicator_iface.h"
19#include "core/fxge/calculate_pitch.h"
20#include "core/fxge/dib/cfx_dibbase.h"
21#include "core/fxge/dib/cfx_dibitmap.h"
22#include "core/fxge/dib/fx_dib.h"
23#include "core/fxge/dib/scanlinecomposer_iface.h"
24
25static_assert(
26 std::is_trivially_destructible<CStretchEngine::PixelWeight>::value,
27 "PixelWeight storage may be re-used without invoking its destructor");
28
29namespace {
30
31size_t TotalBytesForWeightCount(size_t weight_count) {
32 // Always room for one weight even for empty ranges due to declaration
33 // of m_Weights[1] in the header. Don't shrink below this since
34 // CalculateWeights() relies on this later.
35 const size_t extra_weights = weight_count > 0 ? weight_count - 1 : 0;
36 FX_SAFE_SIZE_T total_bytes = extra_weights;
37 total_bytes *= sizeof(CStretchEngine::PixelWeight::m_Weights[0]);
38 total_bytes += sizeof(CStretchEngine::PixelWeight);
39 return total_bytes.ValueOrDie();
40}
41
42} // namespace
43
44// static
46 const FXDIB_ResampleOptions& options,
47 int dest_width,
48 int dest_height,
49 int src_width,
50 int src_height) {
51 return !options.bInterpolateBilinear && !options.bNoSmoothing &&
52 abs(dest_width) != 0 &&
53 abs(dest_height) / 8 <
54 static_cast<long long>(src_width) * src_height / abs(dest_width);
55}
56
58
60
62 int dest_len,
63 int dest_min,
64 int dest_max,
65 int src_len,
66 int src_min,
67 int src_max,
68 const FXDIB_ResampleOptions& options) {
69 // 512MB should be large enough for this while preventing OOM.
70 static constexpr size_t kMaxTableBytesAllowed = 512 * 1024 * 1024;
71
72 // Help the compiler realize that these can't change during a loop iteration:
73 const bool bilinear = options.bInterpolateBilinear;
74
75 m_DestMin = 0;
76 m_ItemSizeBytes = 0;
77 m_WeightTablesSizeBytes = 0;
78 m_WeightTables.clear();
79 if (dest_len == 0)
80 return true;
81
82 if (dest_min > dest_max)
83 return false;
84
85 m_DestMin = dest_min;
86
87 const double scale = static_cast<double>(src_len) / dest_len;
88 const double base = dest_len < 0 ? src_len : 0;
89 const size_t weight_count = static_cast<size_t>(ceil(fabs(scale))) + 1;
90 m_ItemSizeBytes = TotalBytesForWeightCount(weight_count);
91
92 const size_t dest_range = static_cast<size_t>(dest_max - dest_min);
93 const size_t kMaxTableItemsAllowed = kMaxTableBytesAllowed / m_ItemSizeBytes;
94 if (dest_range > kMaxTableItemsAllowed)
95 return false;
96
97 m_WeightTablesSizeBytes = dest_range * m_ItemSizeBytes;
98 m_WeightTables.resize(m_WeightTablesSizeBytes);
100 if (options.bNoSmoothing || fabs(scale) < 1.0f) {
101 for (int dest_pixel = dest_min; dest_pixel < dest_max; ++dest_pixel) {
102 PixelWeight& pixel_weights = *GetPixelWeight(dest_pixel);
103 double src_pos = dest_pixel * scale + scale / 2 + base;
104 if (bilinear) {
105 int src_start = static_cast<int>(floor(src_pos - 0.5));
106 int src_end = static_cast<int>(floor(src_pos + 0.5));
107 src_start = std::max(src_start, src_min);
108 src_end = std::min(src_end, src_max - 1);
109 pixel_weights.SetStartEnd(src_start, src_end, weight_count);
110 if (pixel_weights.m_SrcStart >= pixel_weights.m_SrcEnd) {
111 // Always room for one weight per size calculation.
112 pixel_weights.m_Weights[0] = kFixedPointOne;
113 } else {
114 pixel_weights.m_Weights[1] =
115 FixedFromDouble(src_pos - pixel_weights.m_SrcStart - 0.5f);
116 pixel_weights.m_Weights[0] =
117 kFixedPointOne - pixel_weights.m_Weights[1];
118 }
119 } else {
120 int pixel_pos = static_cast<int>(floor(src_pos));
121 int src_start = std::max(pixel_pos, src_min);
122 int src_end = std::min(pixel_pos, src_max - 1);
123 pixel_weights.SetStartEnd(src_start, src_end, weight_count);
124 pixel_weights.m_Weights[0] = kFixedPointOne;
125 }
126 }
127 return true;
128 }
129
130 for (int dest_pixel = dest_min; dest_pixel < dest_max; ++dest_pixel) {
131 PixelWeight& pixel_weights = *GetPixelWeight(dest_pixel);
132 double src_start = dest_pixel * scale + base;
133 double src_end = src_start + scale;
134 int start_i = floor(std::min(src_start, src_end));
135 int end_i = floor(std::max(src_start, src_end));
136 start_i = std::max(start_i, src_min);
137 end_i = std::min(end_i, src_max - 1);
138 if (start_i > end_i) {
139 start_i = std::min(start_i, src_max - 1);
140 pixel_weights.SetStartEnd(start_i, start_i, weight_count);
141 continue;
142 }
143 pixel_weights.SetStartEnd(start_i, end_i, weight_count);
144 uint32_t remaining = kFixedPointOne;
145 double rounding_error = 0.0;
146 for (int j = start_i; j < end_i; ++j) {
147 double dest_start = (j - base) / scale;
148 double dest_end = (j + 1 - base) / scale;
149 if (dest_start > dest_end) {
150 std::swap(dest_start, dest_end);
151 }
152 double area_start =
153 std::max(dest_start, static_cast<double>(dest_pixel));
154 double area_end =
155 std::min(dest_end, static_cast<double>(dest_pixel + 1));
156 double weight = std::max(0.0, area_end - area_start);
157 uint32_t fixed_weight = FixedFromDouble(weight + rounding_error);
158 pixel_weights.SetWeightForPosition(j, fixed_weight);
159 remaining -= fixed_weight;
160 rounding_error =
161 weight - static_cast<double>(fixed_weight) / kFixedPointOne;
162 }
163 // Note: underflow is defined behaviour for unsigned types and will
164 // result in an out-of-range value.
165 if (remaining && remaining <= kFixedPointOne) {
166 pixel_weights.SetWeightForPosition(end_i, remaining);
167 } else {
168 pixel_weights.RemoveLastWeightAndAdjust(remaining);
169 }
170 }
171 });
172 return true;
173}
174
176 int pixel) const {
177 DCHECK(pixel >= m_DestMin);
178 return reinterpret_cast<const PixelWeight*>(
179 &m_WeightTables[(pixel - m_DestMin) * m_ItemSizeBytes]);
180}
181
183 int pixel) {
184 return const_cast<PixelWeight*>(std::as_const(*this).GetPixelWeight(pixel));
185}
186
188 FXDIB_Format dest_format,
189 int dest_width,
190 int dest_height,
191 const FX_RECT& clip_rect,
192 const RetainPtr<const CFX_DIBBase>& pSrcBitmap,
193 const FXDIB_ResampleOptions& options)
194 : m_DestFormat(dest_format),
195 m_DestBpp(GetBppFromFormat(dest_format)),
203 m_DestWidth(dest_width),
204 m_DestHeight(dest_height),
205 m_DestClip(clip_rect) {
206 if (m_bHasAlpha) {
207 // TODO(crbug.com/42271020): Consider adding support for
208 // `FXDIB_Format::kBgraPremul`
209 DCHECK_EQ(m_DestFormat, FXDIB_Format::kBgra);
211 DCHECK_EQ(m_pSource->GetFormat(), FXDIB_Format::kBgra);
213 }
214
215 std::optional<uint32_t> maybe_size =
216 fxge::CalculatePitch32(m_DestBpp, clip_rect.Width());
217 if (!maybe_size.has_value())
218 return;
219
220 m_DestScanline.resize(maybe_size.value());
221 if (dest_format == FXDIB_Format::kBgrx) {
222 std::fill(m_DestScanline.begin(), m_DestScanline.end(), 255);
223 }
224 m_InterPitch = fxge::CalculatePitch32OrDie(m_DestBpp, m_DestClip.Width());
225 m_ExtraMaskPitch = fxge::CalculatePitch32OrDie(8, m_DestClip.Width());
226 if (options.bNoSmoothing) {
227 m_ResampleOptions.bNoSmoothing = true;
228 } else {
229 if (UseInterpolateBilinear(options, dest_width, dest_height, m_SrcWidth,
230 m_SrcHeight)) {
231 m_ResampleOptions.bInterpolateBilinear = true;
232 } else {
233 m_ResampleOptions = options;
234 }
235 }
236 double scale_x = static_cast<float>(m_SrcWidth) / m_DestWidth;
237 double scale_y = static_cast<float>(m_SrcHeight) / m_DestHeight;
238 double base_x = m_DestWidth > 0 ? 0.0f : m_DestWidth;
239 double base_y = m_DestHeight > 0 ? 0.0f : m_DestHeight;
240 double src_left = scale_x * (clip_rect.left + base_x);
241 double src_right = scale_x * (clip_rect.right + base_x);
242 double src_top = scale_y * (clip_rect.top + base_y);
243 double src_bottom = scale_y * (clip_rect.bottom + base_y);
244 if (src_left > src_right)
245 std::swap(src_left, src_right);
246 if (src_top > src_bottom)
247 std::swap(src_top, src_bottom);
248 m_SrcClip.left = static_cast<int>(floor(src_left));
249 m_SrcClip.right = static_cast<int>(ceil(src_right));
250 m_SrcClip.top = static_cast<int>(floor(src_top));
251 m_SrcClip.bottom = static_cast<int>(ceil(src_bottom));
252 FX_RECT src_rect(0, 0, m_SrcWidth, m_SrcHeight);
253 m_SrcClip.Intersect(src_rect);
254
255 switch (m_SrcBpp) {
256 case 1:
257 m_TransMethod = m_DestBpp == 8 ? TransformMethod::k1BppTo8Bpp
258 : TransformMethod::k1BppToManyBpp;
259 break;
260 case 8:
261 m_TransMethod = m_DestBpp == 8 ? TransformMethod::k8BppTo8Bpp
262 : TransformMethod::k8BppToManyBpp;
263 break;
264 default:
265 m_TransMethod = m_bHasAlpha ? TransformMethod::kManyBpptoManyBppWithAlpha
266 : TransformMethod::kManyBpptoManyBpp;
267 break;
268 }
269}
270
271CStretchEngine::~CStretchEngine() = default;
272
274 while (m_State == State::kHorizontal) {
275 if (ContinueStretchHorz(pPause))
276 return true;
277
278 m_State = State::kVertical;
280 }
281 return false;
282}
283
285 if (m_DestWidth == 0 || m_InterPitch == 0 || m_DestScanline.empty())
286 return false;
287
288 FX_SAFE_SIZE_T safe_size = m_SrcClip.Height();
289 safe_size *= m_InterPitch;
290 const size_t size = safe_size.ValueOrDefault(0);
291 if (size == 0) {
292 return false;
293 }
294 m_InterBuf = FixedSizeDataVector<uint8_t>::TryZeroed(size);
295 if (m_InterBuf.empty()) {
296 return false;
297 }
298 if (!m_WeightTable.CalculateWeights(
299 m_DestWidth, m_DestClip.left, m_DestClip.right, m_SrcWidth,
300 m_SrcClip.left, m_SrcClip.right, m_ResampleOptions)) {
301 return false;
302 }
303 m_CurRow = m_SrcClip.top;
304 m_State = State::kHorizontal;
305 return true;
306}
307
309 if (!m_DestWidth)
310 return false;
311 if (m_pSource->SkipToScanline(m_CurRow, pPause))
312 return true;
313
314 int Bpp = m_DestBpp / 8;
315 static const int kStrechPauseRows = 10;
316 int rows_to_go = kStrechPauseRows;
317 for (; m_CurRow < m_SrcClip.bottom; ++m_CurRow) {
318 if (rows_to_go == 0) {
319 if (pPause && pPause->NeedToPauseNow())
320 return true;
321
322 rows_to_go = kStrechPauseRows;
323 }
324
325 const uint8_t* src_scan = m_pSource->GetScanline(m_CurRow).data();
326 pdfium::span<uint8_t> dest_span = m_InterBuf.subspan(
327 (m_CurRow - m_SrcClip.top) * m_InterPitch, m_InterPitch);
328 size_t dest_span_index = 0;
329 // TODO(npm): reduce duplicated code here
331 switch (m_TransMethod) {
332 case TransformMethod::k1BppTo8Bpp:
333 case TransformMethod::k1BppToManyBpp: {
334 for (int col = m_DestClip.left; col < m_DestClip.right; ++col) {
335 PixelWeight* pWeights = m_WeightTable.GetPixelWeight(col);
336 uint32_t dest_a = 0;
337 for (int j = pWeights->m_SrcStart; j <= pWeights->m_SrcEnd; ++j) {
338 uint32_t pixel_weight = pWeights->GetWeightForPosition(j);
339 if (src_scan[j / 8] & (1 << (7 - j % 8))) {
340 dest_a += pixel_weight * 255;
341 }
342 }
343 dest_span[dest_span_index++] = PixelFromFixed(dest_a);
344 }
345 break;
346 }
347 case TransformMethod::k8BppTo8Bpp: {
348 for (int col = m_DestClip.left; col < m_DestClip.right; ++col) {
349 PixelWeight* pWeights = m_WeightTable.GetPixelWeight(col);
350 uint32_t dest_a = 0;
351 for (int j = pWeights->m_SrcStart; j <= pWeights->m_SrcEnd; ++j) {
352 uint32_t pixel_weight = pWeights->GetWeightForPosition(j);
353 dest_a += pixel_weight * src_scan[j];
354 }
355 dest_span[dest_span_index++] = PixelFromFixed(dest_a);
356 }
357 break;
358 }
359 case TransformMethod::k8BppToManyBpp: {
360 for (int col = m_DestClip.left; col < m_DestClip.right; ++col) {
361 PixelWeight* pWeights = m_WeightTable.GetPixelWeight(col);
362 uint32_t dest_r = 0;
363 uint32_t dest_g = 0;
364 uint32_t dest_b = 0;
365 for (int j = pWeights->m_SrcStart; j <= pWeights->m_SrcEnd; ++j) {
366 uint32_t pixel_weight = pWeights->GetWeightForPosition(j);
367 FX_ARGB argb = m_pSrcPalette[src_scan[j]];
368 if (m_DestFormat == FXDIB_Format::kBgr) {
369 dest_r += pixel_weight * static_cast<uint8_t>(argb >> 16);
370 dest_g += pixel_weight * static_cast<uint8_t>(argb >> 8);
371 dest_b += pixel_weight * static_cast<uint8_t>(argb);
372 } else {
373 dest_b += pixel_weight * static_cast<uint8_t>(argb >> 24);
374 dest_g += pixel_weight * static_cast<uint8_t>(argb >> 16);
375 dest_r += pixel_weight * static_cast<uint8_t>(argb >> 8);
376 }
377 }
378 dest_span[dest_span_index++] = PixelFromFixed(dest_b);
379 dest_span[dest_span_index++] = PixelFromFixed(dest_g);
380 dest_span[dest_span_index++] = PixelFromFixed(dest_r);
381 }
382 break;
383 }
384 case TransformMethod::kManyBpptoManyBpp: {
385 for (int col = m_DestClip.left; col < m_DestClip.right; ++col) {
386 PixelWeight* pWeights = m_WeightTable.GetPixelWeight(col);
387 uint32_t dest_r = 0;
388 uint32_t dest_g = 0;
389 uint32_t dest_b = 0;
390 for (int j = pWeights->m_SrcStart; j <= pWeights->m_SrcEnd; ++j) {
391 uint32_t pixel_weight = pWeights->GetWeightForPosition(j);
392 const uint8_t* src_pixel = src_scan + j * Bpp;
393 dest_b += pixel_weight * (*src_pixel++);
394 dest_g += pixel_weight * (*src_pixel++);
395 dest_r += pixel_weight * (*src_pixel);
396 }
397 dest_span[dest_span_index++] = PixelFromFixed(dest_b);
398 dest_span[dest_span_index++] = PixelFromFixed(dest_g);
399 dest_span[dest_span_index++] = PixelFromFixed(dest_r);
400 dest_span_index += Bpp - 3;
401 }
402 break;
403 }
404 case TransformMethod::kManyBpptoManyBppWithAlpha: {
405 DCHECK(m_bHasAlpha);
406 for (int col = m_DestClip.left; col < m_DestClip.right; ++col) {
407 PixelWeight* pWeights = m_WeightTable.GetPixelWeight(col);
408 uint32_t dest_a = 0;
409 uint32_t dest_r = 0;
410 uint32_t dest_g = 0;
411 uint32_t dest_b = 0;
412 for (int j = pWeights->m_SrcStart; j <= pWeights->m_SrcEnd; ++j) {
413 const uint8_t* src_pixel = src_scan + j * Bpp;
414 uint32_t pixel_weight =
415 pWeights->GetWeightForPosition(j) * src_pixel[3] / 255;
416 dest_b += pixel_weight * (*src_pixel++);
417 dest_g += pixel_weight * (*src_pixel++);
418 dest_r += pixel_weight * (*src_pixel);
419 dest_a += pixel_weight;
420 }
421 dest_span[dest_span_index++] = PixelFromFixed(dest_b);
422 dest_span[dest_span_index++] = PixelFromFixed(dest_g);
423 dest_span[dest_span_index++] = PixelFromFixed(dest_r);
424 dest_span[dest_span_index] = PixelFromFixed(255 * dest_a);
425 dest_span_index += Bpp - 3;
426 }
427 break;
428 }
429 }
430 });
431 rows_to_go--;
432 }
433 return false;
434}
435
437 if (m_DestHeight == 0)
438 return;
439
440 WeightTable table;
441 if (!table.CalculateWeights(m_DestHeight, m_DestClip.top, m_DestClip.bottom,
442 m_SrcHeight, m_SrcClip.top, m_SrcClip.bottom,
443 m_ResampleOptions)) {
444 return;
445 }
446
447 const int DestBpp = m_DestBpp / 8;
449 for (int row = m_DestClip.top; row < m_DestClip.bottom; ++row) {
450 unsigned char* dest_scan = m_DestScanline.data();
451 PixelWeight* pWeights = table.GetPixelWeight(row);
452 switch (m_TransMethod) {
453 case TransformMethod::k1BppTo8Bpp:
454 case TransformMethod::k1BppToManyBpp:
455 case TransformMethod::k8BppTo8Bpp: {
456 for (int col = m_DestClip.left; col < m_DestClip.right; ++col) {
457 pdfium::span<const uint8_t> src_span =
458 m_InterBuf.subspan((col - m_DestClip.left) * DestBpp);
459 uint32_t dest_a = 0;
460 for (int j = pWeights->m_SrcStart; j <= pWeights->m_SrcEnd; ++j) {
461 uint32_t pixel_weight = pWeights->GetWeightForPosition(j);
462 dest_a +=
463 pixel_weight * src_span[(j - m_SrcClip.top) * m_InterPitch];
464 }
465 *dest_scan = PixelFromFixed(dest_a);
466 dest_scan += DestBpp;
467 }
468 break;
469 }
470 case TransformMethod::k8BppToManyBpp:
471 case TransformMethod::kManyBpptoManyBpp: {
472 for (int col = m_DestClip.left; col < m_DestClip.right; ++col) {
473 pdfium::span<const uint8_t> src_span =
474 m_InterBuf.subspan((col - m_DestClip.left) * DestBpp);
475 uint32_t dest_r = 0;
476 uint32_t dest_g = 0;
477 uint32_t dest_b = 0;
478 for (int j = pWeights->m_SrcStart; j <= pWeights->m_SrcEnd; ++j) {
479 uint32_t pixel_weight = pWeights->GetWeightForPosition(j);
480 pdfium::span<const uint8_t> src_pixel =
481 src_span.subspan((j - m_SrcClip.top) * m_InterPitch, 3);
482 dest_b += pixel_weight * src_pixel[0];
483 dest_g += pixel_weight * src_pixel[1];
484 dest_r += pixel_weight * src_pixel[2];
485 }
486 dest_scan[0] = PixelFromFixed(dest_b);
487 dest_scan[1] = PixelFromFixed(dest_g);
488 dest_scan[2] = PixelFromFixed(dest_r);
489 dest_scan += DestBpp;
490 }
491 break;
492 }
493 case TransformMethod::kManyBpptoManyBppWithAlpha: {
494 DCHECK(m_bHasAlpha);
495 for (int col = m_DestClip.left; col < m_DestClip.right; ++col) {
496 pdfium::span<const uint8_t> src_span =
497 m_InterBuf.subspan((col - m_DestClip.left) * DestBpp);
498 uint32_t dest_a = 0;
499 uint32_t dest_r = 0;
500 uint32_t dest_g = 0;
501 uint32_t dest_b = 0;
502 constexpr size_t kPixelBytes = 4;
503 for (int j = pWeights->m_SrcStart; j <= pWeights->m_SrcEnd; ++j) {
504 uint32_t pixel_weight = pWeights->GetWeightForPosition(j);
505 pdfium::span<const uint8_t> src_pixel = src_span.subspan(
506 (j - m_SrcClip.top) * m_InterPitch, kPixelBytes);
507 dest_b += pixel_weight * src_pixel[0];
508 dest_g += pixel_weight * src_pixel[1];
509 dest_r += pixel_weight * src_pixel[2];
510 dest_a += pixel_weight * src_pixel[3];
511 }
512 if (dest_a) {
513 int r = static_cast<uint32_t>(dest_r) * 255 / dest_a;
514 int g = static_cast<uint32_t>(dest_g) * 255 / dest_a;
515 int b = static_cast<uint32_t>(dest_b) * 255 / dest_a;
516 dest_scan[0] = std::clamp(b, 0, 255);
517 dest_scan[1] = std::clamp(g, 0, 255);
518 dest_scan[2] = std::clamp(r, 0, 255);
519 }
520 dest_scan[3] = PixelFromFixed(dest_a);
521 dest_scan += DestBpp;
522 }
523 break;
524 }
525 }
526 m_pDestBitmap->ComposeScanline(row - m_DestClip.top, m_DestScanline);
527 }
528 });
529}
#define DCHECK
Definition check.h:33
#define DCHECK_EQ(x, y)
Definition check_op.h:17
PixelWeight * GetPixelWeight(int pixel)
const PixelWeight * GetPixelWeight(int pixel) const
bool CalculateWeights(int dest_len, int dest_min, int dest_max, int src_len, int src_min, int src_max, const FXDIB_ResampleOptions &options)
bool Continue(PauseIndicatorIface *pPause)
bool ContinueStretchHorz(PauseIndicatorIface *pPause)
static bool UseInterpolateBilinear(const FXDIB_ResampleOptions &options, int dest_width, int dest_height, int src_width, int src_height)
static uint32_t FixedFromDouble(double d)
static uint8_t PixelFromFixed(uint32_t fixed)
CStretchEngine(ScanlineComposerIface *pDestBitmap, FXDIB_Format dest_format, int dest_width, int dest_height, const FX_RECT &clip_rect, const RetainPtr< const CFX_DIBBase > &pSrcBitmap, const FXDIB_ResampleOptions &options)
static constexpr uint32_t kFixedPointOne
virtual bool NeedToPauseNow()=0
#define UNSAFE_TODO(...)
uint32_t FX_ARGB
Definition fx_dib.h:36
int GetBppFromFormat(FXDIB_Format format)
Definition fx_dib.h:160
FXDIB_Format
Definition fx_dib.h:21
uint32_t CalculatePitch32OrDie(int bits_per_pixel, int width_in_pixels)
void RemoveLastWeightAndAdjust(uint32_t weight_change)
uint32_t GetWeightForPosition(int position) const
void SetWeightForPosition(int position, uint32_t weight)
int Height() const
int32_t bottom
int32_t right
int Width() const
FX_RECT(const FX_RECT &that)=default
int32_t top
int32_t left
void Intersect(const FX_RECT &src)
constexpr FX_RECT(int l, int t, int r, int b)