mirror of
https://github.com/thorvg/thorvg.git
synced 2025-06-08 13:43:43 +00:00
sw_engine Raster: Improving image quality in image scale down
An average value sampled based on adjacent pixels of the target pixel is used.
This commit is contained in:
parent
dcbcc409df
commit
56d31775ff
1 changed files with 211 additions and 10 deletions
|
@ -113,6 +113,26 @@ static uint32_t _applyBilinearInterpolation(const uint32_t *img, uint32_t w, uin
|
||||||
return COLOR_INTERPOLATE(COLOR_INTERPOLATE(c1, 255 - dX, c2, dX), 255 - dY, COLOR_INTERPOLATE(c4, 255 - dX, c3, dX), dY);
|
return COLOR_INTERPOLATE(COLOR_INTERPOLATE(c1, 255 - dX, c2, dX), 255 - dY, COLOR_INTERPOLATE(c4, 255 - dX, c3, dX), dY);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static uint32_t _average2Nx2NPixel(SwSurface* surface, const uint32_t *img, uint32_t w, uint32_t h, uint32_t rX, uint32_t rY, uint32_t n)
|
||||||
|
{
|
||||||
|
uint32_t c[4] = { 0 };
|
||||||
|
auto n2 = n * n;
|
||||||
|
auto source = img + rX - n + (rY - n) * w;
|
||||||
|
for (auto y = rY - n; y < rY + n; ++y) {
|
||||||
|
auto src = source;
|
||||||
|
for (auto x = rX - n; x < rX + n; ++x, ++src) {
|
||||||
|
c[0] += *src >> 24;
|
||||||
|
c[1] += (*src >> 16) & 0xff;
|
||||||
|
c[2] += (*src >> 8) & 0xff;
|
||||||
|
c[3] += *src & 0xff;
|
||||||
|
}
|
||||||
|
source += w;
|
||||||
|
}
|
||||||
|
for (auto i = 0; i < 4; ++i) {
|
||||||
|
c[i] = (c[i] >> 2) / n2;
|
||||||
|
}
|
||||||
|
return (c[0] << 24) | (c[1] << 16) | (c[2] << 8) | c[3];
|
||||||
|
}
|
||||||
|
|
||||||
/************************************************************************/
|
/************************************************************************/
|
||||||
/* Rect */
|
/* Rect */
|
||||||
|
@ -332,6 +352,7 @@ static bool _rasterTranslucentImageRle(SwSurface* surface, const SwRleData* rle,
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
static bool _rasterTranslucentUpScaleImageRle(SwSurface* surface, const SwRleData* rle, uint32_t *img, uint32_t w, uint32_t h, uint32_t opacity, const Matrix* invTransform)
|
static bool _rasterTranslucentUpScaleImageRle(SwSurface* surface, const SwRleData* rle, uint32_t *img, uint32_t w, uint32_t h, uint32_t opacity, const Matrix* invTransform)
|
||||||
{
|
{
|
||||||
auto span = rle->spans;
|
auto span = rle->spans;
|
||||||
|
@ -356,6 +377,30 @@ static bool _rasterTranslucentUpScaleImageRle(SwSurface* surface, const SwRleDat
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
static bool _rasterTranslucentDownScaleImageRle(SwSurface* surface, const SwRleData* rle, uint32_t *img, uint32_t w, uint32_t h, uint32_t opacity, const Matrix* invTransform, float scaling)
|
||||||
|
{
|
||||||
|
uint32_t halfScaling = static_cast<uint32_t>(0.5f / scaling);
|
||||||
|
if (halfScaling == 0) halfScaling = 1;
|
||||||
|
auto span = rle->spans;
|
||||||
|
for (uint32_t i = 0; i < rle->size; ++i, ++span) {
|
||||||
|
auto ey1 = span->y * invTransform->e12 + invTransform->e13;
|
||||||
|
auto ey2 = span->y * invTransform->e22 + invTransform->e23;
|
||||||
|
auto dst = &surface->buffer[span->y * surface->stride + span->x];
|
||||||
|
auto alpha = ALPHA_MULTIPLY(span->coverage, opacity);
|
||||||
|
for (uint32_t x = 0; x < span->len; ++x, ++dst) {
|
||||||
|
auto rX = static_cast<uint32_t>(roundf((span->x + x) * invTransform->e11 + ey1));
|
||||||
|
auto rY = static_cast<uint32_t>(roundf((span->x + x) * invTransform->e21 + ey2));
|
||||||
|
if (rX >= w || rY >= h) continue;
|
||||||
|
uint32_t src;
|
||||||
|
if (rX < halfScaling || rY < halfScaling || rX >= w - halfScaling || rY >= h - halfScaling) src = ALPHA_BLEND(img[rY * w + rX], alpha); //TODO: need to use image's stride
|
||||||
|
else src = ALPHA_BLEND(_average2Nx2NPixel(surface, img, w, h, rX, rY, halfScaling), alpha); //TODO: need to use image's stride
|
||||||
|
*dst = src + ALPHA_BLEND(*dst, 255 - surface->blender.alpha(src));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
static bool _rasterImageRle(SwSurface* surface, SwRleData* rle, uint32_t *img, uint32_t w, uint32_t h)
|
static bool _rasterImageRle(SwSurface* surface, SwRleData* rle, uint32_t *img, uint32_t w, uint32_t h)
|
||||||
{
|
{
|
||||||
auto span = rle->spans;
|
auto span = rle->spans;
|
||||||
|
@ -416,6 +461,30 @@ static bool _rasterUpScaleImageRle(SwSurface* surface, SwRleData* rle, uint32_t
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
static bool _rasterDownScaleImageRle(SwSurface* surface, SwRleData* rle, uint32_t *img, uint32_t w, uint32_t h, const Matrix* invTransform, float scaling)
|
||||||
|
{
|
||||||
|
uint32_t halfScaling = static_cast<uint32_t>(0.5f / scaling);
|
||||||
|
if (halfScaling == 0) halfScaling = 1;
|
||||||
|
auto span = rle->spans;
|
||||||
|
|
||||||
|
for (uint32_t i = 0; i < rle->size; ++i, ++span) {
|
||||||
|
auto ey1 = span->y * invTransform->e12 + invTransform->e13;
|
||||||
|
auto ey2 = span->y * invTransform->e22 + invTransform->e23;
|
||||||
|
auto dst = &surface->buffer[span->y * surface->stride + span->x];
|
||||||
|
for (uint32_t x = 0; x < span->len; ++x, ++dst) {
|
||||||
|
auto rX = static_cast<uint32_t>(roundf((span->x + x) * invTransform->e11 + ey1));
|
||||||
|
auto rY = static_cast<uint32_t>(roundf((span->x + x) * invTransform->e21 + ey2));
|
||||||
|
if (rX >= w || rY >= h) continue;
|
||||||
|
uint32_t src;
|
||||||
|
if (rX < halfScaling || rY < halfScaling || rX >= w - halfScaling || rY >= h - halfScaling) src = ALPHA_BLEND(img[rY * w + rX], span->coverage); //TODO: need to use image's stride
|
||||||
|
else src = ALPHA_BLEND(_average2Nx2NPixel(surface, img, w, h, rX, rY, halfScaling), span->coverage); //TODO: need to use image's stride
|
||||||
|
*dst = src + ALPHA_BLEND(*dst, 255 - surface->blender.alpha(src));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
static bool _translucentImage(SwSurface* surface, const uint32_t *img, uint32_t w, TVG_UNUSED uint32_t h, uint32_t opacity, const SwBBox& region, const Matrix* invTransform)
|
static bool _translucentImage(SwSurface* surface, const uint32_t *img, uint32_t w, TVG_UNUSED uint32_t h, uint32_t opacity, const SwBBox& region, const Matrix* invTransform)
|
||||||
{
|
{
|
||||||
auto dbuffer = &surface->buffer[region.min.y * surface->stride + region.min.x];
|
auto dbuffer = &surface->buffer[region.min.y * surface->stride + region.min.x];
|
||||||
|
@ -601,6 +670,105 @@ static bool _rasterTranslucentUpScaleImage(SwSurface* surface, const uint32_t *i
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
static bool _translucentDownScaleImage(SwSurface* surface, const uint32_t *img, uint32_t w, TVG_UNUSED uint32_t h, uint32_t opacity, const SwBBox& region, const Matrix* invTransform, float scaling)
|
||||||
|
{
|
||||||
|
uint32_t halfScaling = static_cast<uint32_t>(0.5f / scaling);
|
||||||
|
if (halfScaling == 0) halfScaling = 1;
|
||||||
|
auto dbuffer = &surface->buffer[region.min.y * surface->stride + region.min.x];
|
||||||
|
|
||||||
|
for (auto y = region.min.y; y < region.max.y; ++y) {
|
||||||
|
auto dst = dbuffer;
|
||||||
|
auto ey1 = y * invTransform->e12 + invTransform->e13;
|
||||||
|
auto ey2 = y * invTransform->e22 + invTransform->e23;
|
||||||
|
for (auto x = region.min.x; x < region.max.x; ++x, ++dst) {
|
||||||
|
auto rX = static_cast<uint32_t>(roundf(x * invTransform->e11 + ey1));
|
||||||
|
auto rY = static_cast<uint32_t>(roundf(x * invTransform->e21 + ey2));
|
||||||
|
if (rX >= w || rY >= h) continue;
|
||||||
|
uint32_t src;
|
||||||
|
if (rX < halfScaling || rY < halfScaling || rX >= w - halfScaling || rY >= h - halfScaling) src = ALPHA_BLEND(img[rX + (rY * w)], opacity);
|
||||||
|
else src = ALPHA_BLEND(_average2Nx2NPixel(surface, img, w, h, rX, rY, halfScaling), opacity);
|
||||||
|
*dst = src + ALPHA_BLEND(*dst, 255 - surface->blender.alpha(src));
|
||||||
|
}
|
||||||
|
dbuffer += surface->stride;
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
static bool _translucentDownScaleImageAlphaMask(SwSurface* surface, const uint32_t *img, uint32_t w, TVG_UNUSED uint32_t h, uint32_t opacity, const SwBBox& region, const Matrix* invTransform, float scaling)
|
||||||
|
{
|
||||||
|
TVGLOG("SW_ENGINE", "Transformed Image Alpha Mask Composition");
|
||||||
|
uint32_t halfScaling = static_cast<uint32_t>(0.5f / scaling);
|
||||||
|
if (halfScaling == 0) halfScaling = 1;
|
||||||
|
|
||||||
|
auto dbuffer = &surface->buffer[region.min.y * surface->stride + region.min.x];
|
||||||
|
auto cbuffer = &surface->compositor->image.data[region.min.y * surface->stride + region.min.x];
|
||||||
|
|
||||||
|
for (auto y = region.min.y; y < region.max.y; ++y) {
|
||||||
|
auto dst = dbuffer;
|
||||||
|
auto cmp = cbuffer;
|
||||||
|
float ey1 = y * invTransform->e12 + invTransform->e13;
|
||||||
|
float ey2 = y * invTransform->e22 + invTransform->e23;
|
||||||
|
for (auto x = region.min.x; x < region.max.x; ++x, ++dst, ++cmp) {
|
||||||
|
auto rX = static_cast<uint32_t>(roundf(x * invTransform->e11 + ey1));
|
||||||
|
auto rY = static_cast<uint32_t>(roundf(x * invTransform->e21 + ey2));
|
||||||
|
if (rX >= w || rY >= h) continue;
|
||||||
|
uint32_t src;
|
||||||
|
if (rX < halfScaling || rY < halfScaling || rX >= w - halfScaling || rY >= h - halfScaling) src = ALPHA_BLEND(img[rX + (rY * w)], ALPHA_MULTIPLY(opacity, surface->blender.alpha(*cmp))); //TODO: need to use image's stride
|
||||||
|
else src = ALPHA_BLEND(_average2Nx2NPixel(surface, img, w, h, rX, rY, halfScaling), ALPHA_MULTIPLY(opacity, surface->blender.alpha(*cmp))); //TODO: need to use image's stride
|
||||||
|
*dst = src + ALPHA_BLEND(*dst, 255 - surface->blender.alpha(src));
|
||||||
|
}
|
||||||
|
dbuffer += surface->stride;
|
||||||
|
cbuffer += surface->stride;
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
static bool _translucentDownScaleImageInvAlphaMask(SwSurface* surface, const uint32_t *img, uint32_t w, uint32_t h, uint32_t opacity, const SwBBox& region, const Matrix* invTransform, float scaling)
|
||||||
|
{
|
||||||
|
TVGLOG("SW_ENGINE", "Transformed Image Inverse Alpha Mask Composition");
|
||||||
|
uint32_t halfScaling = static_cast<uint32_t>(0.5f / scaling);
|
||||||
|
if (halfScaling == 0) halfScaling = 1;
|
||||||
|
|
||||||
|
auto dbuffer = &surface->buffer[region.min.y * surface->stride + region.min.x];
|
||||||
|
auto cbuffer = &surface->compositor->image.data[region.min.y * surface->stride + region.min.x];
|
||||||
|
|
||||||
|
for (auto y = region.min.y; y < region.max.y; ++y) {
|
||||||
|
auto dst = dbuffer;
|
||||||
|
auto cmp = cbuffer;
|
||||||
|
float ey1 = y * invTransform->e12 + invTransform->e13;
|
||||||
|
float ey2 = y * invTransform->e22 + invTransform->e23;
|
||||||
|
for (auto x = region.min.x; x < region.max.x; ++x, ++dst, ++cmp) {
|
||||||
|
auto rX = static_cast<uint32_t>(roundf(x * invTransform->e11 + ey1));
|
||||||
|
auto rY = static_cast<uint32_t>(roundf(x * invTransform->e21 + ey2));
|
||||||
|
if (rX >= w || rY >= h) continue;
|
||||||
|
uint32_t src;
|
||||||
|
if (rX < halfScaling || rY < halfScaling || rX >= w - halfScaling || rY >= h - halfScaling) src = ALPHA_BLEND(img[rX + (rY * w)], ALPHA_MULTIPLY(opacity, 255 - surface->blender.alpha(*cmp))); //TODO: need to use image's stride
|
||||||
|
else src = ALPHA_BLEND(_average2Nx2NPixel(surface, img, w, h, rX, rY, halfScaling), ALPHA_MULTIPLY(opacity, 255 - surface->blender.alpha(*cmp))); //TODO: need to use image's stride
|
||||||
|
*dst = src + ALPHA_BLEND(*dst, 255 - surface->blender.alpha(src));
|
||||||
|
}
|
||||||
|
dbuffer += surface->stride;
|
||||||
|
cbuffer += surface->stride;
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
static bool _rasterTranslucentDownScaleImage(SwSurface* surface, const uint32_t *img, uint32_t w, uint32_t h, uint32_t opacity, const SwBBox& region, const Matrix* invTransform, float scaling)
|
||||||
|
{
|
||||||
|
if (surface->compositor) {
|
||||||
|
if (surface->compositor->method == CompositeMethod::AlphaMask) {
|
||||||
|
return _translucentDownScaleImageAlphaMask(surface, img, w, h, opacity, region, invTransform, scaling);
|
||||||
|
}
|
||||||
|
if (surface->compositor->method == CompositeMethod::InvAlphaMask) {
|
||||||
|
return _translucentDownScaleImageInvAlphaMask(surface, img, w, h, opacity, region, invTransform, scaling);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return _translucentDownScaleImage(surface, img, w, h, opacity, region, invTransform, scaling);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
static bool _translucentImage(SwSurface* surface, uint32_t *img, uint32_t w, uint32_t h, uint32_t opacity, const SwBBox& region)
|
static bool _translucentImage(SwSurface* surface, uint32_t *img, uint32_t w, uint32_t h, uint32_t opacity, const SwBBox& region)
|
||||||
{
|
{
|
||||||
auto dbuffer = &surface->buffer[region.min.y * surface->stride + region.min.x];
|
auto dbuffer = &surface->buffer[region.min.y * surface->stride + region.min.x];
|
||||||
|
@ -744,6 +912,31 @@ static bool _rasterUpScaleImage(SwSurface* surface, const uint32_t *img, uint32_
|
||||||
}
|
}
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
static bool _rasterDownScaleImage(SwSurface* surface, const uint32_t *img, uint32_t w, uint32_t h, const SwBBox& region, const Matrix* invTransform, float scaling)
|
||||||
|
{
|
||||||
|
uint32_t halfScaling = static_cast<uint32_t>(0.5f / scaling);
|
||||||
|
|
||||||
|
if (halfScaling == 0) halfScaling = 1;
|
||||||
|
for (auto y = region.min.y; y < region.max.y; ++y) {
|
||||||
|
auto dst = &surface->buffer[y * surface->stride + region.min.x];
|
||||||
|
auto ey1 = y * invTransform->e12 + invTransform->e13;
|
||||||
|
auto ey2 = y * invTransform->e22 + invTransform->e23;
|
||||||
|
for (auto x = region.min.x; x < region.max.x; ++x, ++dst) {
|
||||||
|
auto rX = static_cast<uint32_t>(roundf(x * invTransform->e11 + ey1));
|
||||||
|
auto rY = static_cast<uint32_t>(roundf(x * invTransform->e21 + ey2));
|
||||||
|
if (rX >= w || rY >= h) continue;
|
||||||
|
uint32_t src;
|
||||||
|
if (rX < halfScaling || rY < halfScaling || rX >= w - halfScaling || rY >= h - halfScaling) src = img[rX + (rY * w)];
|
||||||
|
else src = _average2Nx2NPixel(surface, img, w, h, rX, rY, halfScaling);
|
||||||
|
*dst = src + ALPHA_BLEND(*dst, 255 - surface->blender.alpha(src));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
/************************************************************************/
|
/************************************************************************/
|
||||||
/* Gradient */
|
/* Gradient */
|
||||||
/************************************************************************/
|
/************************************************************************/
|
||||||
|
@ -1364,15 +1557,19 @@ bool rasterClear(SwSurface* surface)
|
||||||
bool rasterImage(SwSurface* surface, SwImage* image, const Matrix* transform, const SwBBox& bbox, uint32_t opacity)
|
bool rasterImage(SwSurface* surface, SwImage* image, const Matrix* transform, const SwBBox& bbox, uint32_t opacity)
|
||||||
{
|
{
|
||||||
Matrix invTransform;
|
Matrix invTransform;
|
||||||
auto isUpScaling = false;
|
float scaling = 1.0f;
|
||||||
|
|
||||||
if (transform) {
|
if (transform) {
|
||||||
if (!_inverse(transform, &invTransform)) return false;
|
if (!_inverse(transform, &invTransform)) return false;
|
||||||
isUpScaling = (transform->e11 * transform->e11) + (transform->e21 * transform->e21) > 1 ? true : false;
|
scaling = sqrt((transform->e11 * transform->e11) + (transform->e21 * transform->e21));
|
||||||
|
auto scalingY = sqrt((transform->e22 * transform->e22) + (transform->e12 * transform->e12));
|
||||||
|
//TODO:If the x and y axis scaling is different, a separate algorithm for each axis should be applied.
|
||||||
|
if (scaling != scalingY) scaling = 1.0f;
|
||||||
}
|
}
|
||||||
else invTransform = {1, 0, 0, 0, 1, 0, 0, 0, 1};
|
else invTransform = {1, 0, 0, 0, 1, 0, 0, 0, 1};
|
||||||
|
|
||||||
auto translucent = _translucent(surface, opacity);
|
auto translucent = _translucent(surface, opacity);
|
||||||
|
const float downScalingFactor = 0.5f;
|
||||||
|
|
||||||
if (image->rle) {
|
if (image->rle) {
|
||||||
//Fast track
|
//Fast track
|
||||||
|
@ -1382,11 +1579,13 @@ bool rasterImage(SwSurface* surface, SwImage* image, const Matrix* transform, co
|
||||||
return _rasterImageRle(surface, image->rle, image->data, image->w, image->h);
|
return _rasterImageRle(surface, image->rle, image->data, image->w, image->h);
|
||||||
} else {
|
} else {
|
||||||
if (translucent) {
|
if (translucent) {
|
||||||
if (isUpScaling) return _rasterTranslucentUpScaleImageRle(surface, image->rle, image->data, image->w, image->h, opacity, &invTransform);
|
if (fabsf(scaling - 1.0f) <= FLT_EPSILON) return _rasterTranslucentImageRle(surface, image->rle, image->data, image->w, image->h, opacity, &invTransform);
|
||||||
return _rasterTranslucentImageRle(surface, image->rle, image->data, image->w, image->h, opacity, &invTransform);
|
else if (scaling < downScalingFactor) return _rasterTranslucentDownScaleImageRle(surface, image->rle, image->data, image->w, image->h, opacity, &invTransform, scaling);
|
||||||
|
else return _rasterTranslucentUpScaleImageRle(surface, image->rle, image->data, image->w, image->h, opacity, &invTransform);
|
||||||
}
|
}
|
||||||
if (isUpScaling) return _rasterUpScaleImageRle(surface, image->rle, image->data, image->w, image->h, &invTransform);
|
if (fabsf(scaling - 1.0f) <= FLT_EPSILON) return _rasterImageRle(surface, image->rle, image->data, image->w, image->h, &invTransform);
|
||||||
return _rasterImageRle(surface, image->rle, image->data, image->w, image->h, &invTransform);
|
else if (scaling < downScalingFactor) return _rasterDownScaleImageRle(surface, image->rle, image->data, image->w, image->h, &invTransform, scaling);
|
||||||
|
else return _rasterUpScaleImageRle(surface, image->rle, image->data, image->w, image->h, &invTransform);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
|
@ -1397,11 +1596,13 @@ bool rasterImage(SwSurface* surface, SwImage* image, const Matrix* transform, co
|
||||||
return _rasterImage(surface, image->data, image->w, image->h, bbox);
|
return _rasterImage(surface, image->data, image->w, image->h, bbox);
|
||||||
} else {
|
} else {
|
||||||
if (translucent) {
|
if (translucent) {
|
||||||
if (isUpScaling) return _rasterTranslucentUpScaleImage(surface, image->data, image->w, image->h, opacity, bbox, &invTransform);
|
if (fabsf(scaling - 1.0f) <= FLT_EPSILON) return _rasterTranslucentImage(surface, image->data, image->w, image->h, opacity, bbox, &invTransform);
|
||||||
return _rasterTranslucentImage(surface, image->data, image->w, image->h, opacity, bbox, &invTransform);
|
else if (scaling < downScalingFactor) return _rasterTranslucentDownScaleImage(surface, image->data, image->w, image->h, opacity, bbox, &invTransform, scaling);
|
||||||
|
else return _rasterTranslucentUpScaleImage(surface, image->data, image->w, image->h, opacity, bbox, &invTransform);
|
||||||
}
|
}
|
||||||
if (isUpScaling) return _rasterUpScaleImage(surface, image->data, image->w, image->h, bbox, &invTransform);
|
if (fabsf(scaling - 1.0f) <= FLT_EPSILON) return _rasterImage(surface, image->data, image->w, image->h, bbox, &invTransform);
|
||||||
return _rasterImage(surface, image->data, image->w, image->h, bbox, &invTransform);
|
else if (scaling < downScalingFactor) return _rasterDownScaleImage(surface, image->data, image->w, image->h, bbox, &invTransform, scaling);
|
||||||
|
else return _rasterUpScaleImage(surface, image->data, image->w, image->h, bbox, &invTransform);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
Loading…
Add table
Reference in a new issue