| /* |
| * Copyright 2026 Google LLC |
| * |
| * Use of this source code is governed by a BSD-style license that can be |
| * found in the LICENSE file. |
| */ |
| |
| #include "include/core/SkImageInfo.h" |
| #include "src/core/SkReadPixelsRec.h" |
| #include "src/core/SkWritePixelsRec.h" |
| #include "tests/Test.h" |
| |
| #include <climits> |
| #include <cstddef> |
| #include <cstdint> |
| #include <vector> |
| |
| DEF_TEST(ReadPixelsRec_trim, reporter) { |
| constexpr int W = 100; |
| constexpr int H = 100; |
| const SkImageInfo info = SkImageInfo::MakeN32Premul(W, H); |
| const size_t rowBytes = info.minRowBytes(); |
| std::vector<char> storage(H * rowBytes); |
| void* pixels = storage.data(); |
| |
| { |
| skiatest::ReporterContext ctx(reporter, "Normal valid trim"); |
| SkReadPixelsRec rec(info, pixels, rowBytes, 0, 0); |
| REPORTER_ASSERT(reporter, rec.trim(W, H)); |
| REPORTER_ASSERT(reporter, rec.fPixels == pixels); |
| REPORTER_ASSERT(reporter, rec.fInfo.width() == W); |
| REPORTER_ASSERT(reporter, rec.fInfo.height() == H); |
| } |
| |
| { |
| skiatest::ReporterContext ctx(reporter, "Trim with negative x, y"); |
| SkReadPixelsRec rec(info, pixels, rowBytes, -10, -10); |
| REPORTER_ASSERT(reporter, rec.trim(W, H)); |
| // fPixels should be adjusted: pixels + 10 * rowBytes + 10 * 4 |
| REPORTER_ASSERT(reporter, rec.fPixels == |
| (char*)pixels + 10 * rowBytes + 10 * info.bytesPerPixel()); |
| REPORTER_ASSERT(reporter, rec.fInfo.width() == W - 10); |
| REPORTER_ASSERT(reporter, rec.fInfo.height() == H - 10); |
| REPORTER_ASSERT(reporter, rec.fX == 0); |
| REPORTER_ASSERT(reporter, rec.fY == 0); |
| } |
| |
| { |
| skiatest::ReporterContext ctx(reporter, "Trim with x, y partially outside"); |
| SkReadPixelsRec rec(info, pixels, rowBytes, 50, 50); |
| REPORTER_ASSERT(reporter, rec.trim(W, H)); |
| REPORTER_ASSERT(reporter, rec.fPixels == pixels); |
| REPORTER_ASSERT(reporter, rec.fInfo.width() == 50); |
| REPORTER_ASSERT(reporter, rec.fInfo.height() == 50); |
| REPORTER_ASSERT(reporter, rec.fX == 50); |
| REPORTER_ASSERT(reporter, rec.fY == 50); |
| } |
| |
| { |
| skiatest::ReporterContext ctx(reporter, "Trim with x, y completely outside (positive)"); |
| SkReadPixelsRec rec(info, pixels, rowBytes, 150, 150); |
| REPORTER_ASSERT(reporter, !rec.trim(W, H)); |
| } |
| |
| { |
| skiatest::ReporterContext ctx(reporter, "Trim with x, y completely outside (negative)"); |
| SkReadPixelsRec rec(info, pixels, rowBytes, -150, -150); |
| REPORTER_ASSERT(reporter, !rec.trim(W, H)); |
| } |
| |
| { |
| skiatest::ReporterContext ctx(reporter, "Explicitly trigger y_offset overflow"); |
| // arbitrarily large value that will overflow when multiplied by hugeY |
| size_t hugeRowBytes = static_cast<size_t>(INT_MAX) >> 13; |
| int hugeY = INT_MIN; |
| SkReadPixelsRec rec(info, pixels, hugeRowBytes, 0, hugeY); |
| REPORTER_ASSERT(reporter, !rec.trim(W, H)); |
| } |
| |
| { |
| if (sizeof(size_t) == 4) { |
| skiatest::ReporterContext ctx(reporter, "Explicitly trigger x_offset overflow"); |
| int hugeX = INT_MIN; |
| SkReadPixelsRec rec(info, pixels, rowBytes, hugeX, 0); |
| REPORTER_ASSERT(reporter, !rec.trim(W, H)); |
| } |
| } |
| |
| { |
| skiatest::ReporterContext ctx(reporter, "Explicitly trigger total overflow"); |
| SkReadPixelsRec rec(info, pixels, SIZE_MAX, -1, -1); |
| REPORTER_ASSERT(reporter, !rec.trim(W, H)); |
| } |
| |
| { |
| skiatest::ReporterContext ctx(reporter, "Trim with large rowBytes (initial check)"); |
| SkImageInfo hugeInfo = SkImageInfo::MakeN32Premul(1 << 30, 1); |
| SkReadPixelsRec rec(hugeInfo, pixels, 100, 0, 0); |
| REPORTER_ASSERT(reporter, !rec.trim(W, H)); |
| } |
| |
| { |
| skiatest::ReporterContext ctx(reporter, "Invalid ImageInfo (width <= 0)"); |
| SkReadPixelsRec rec(SkImageInfo::MakeN32Premul(-1, 100), pixels, rowBytes, 0, 0); |
| REPORTER_ASSERT(reporter, !rec.trim(W, H)); |
| } |
| |
| { |
| skiatest::ReporterContext ctx(reporter, "rowBytes too small"); |
| SkReadPixelsRec rec(info, pixels, info.minRowBytes() - 1, 0, 0); |
| REPORTER_ASSERT(reporter, !rec.trim(W, H)); |
| } |
| } |
| |
| DEF_TEST(WritePixelsRec_trim, reporter) { |
| constexpr int W = 100; |
| constexpr int H = 100; |
| const SkImageInfo info = SkImageInfo::MakeN32Premul(W, H); |
| const size_t rowBytes = info.minRowBytes(); |
| std::vector<char> storage(H * rowBytes); |
| const void* pixels = storage.data(); |
| |
| { |
| skiatest::ReporterContext ctx(reporter, "Normal valid trim"); |
| SkWritePixelsRec rec(info, pixels, rowBytes, 0, 0); |
| REPORTER_ASSERT(reporter, rec.trim(W, H)); |
| REPORTER_ASSERT(reporter, rec.fPixels == pixels); |
| REPORTER_ASSERT(reporter, rec.fInfo.width() == W); |
| REPORTER_ASSERT(reporter, rec.fInfo.height() == H); |
| } |
| |
| { |
| skiatest::ReporterContext ctx(reporter, "Trim with negative x, y"); |
| SkWritePixelsRec rec(info, pixels, rowBytes, -10, -10); |
| REPORTER_ASSERT(reporter, rec.trim(W, H)); |
| // fPixels should be adjusted: pixels + 10 * rowBytes + 10 * 4 |
| REPORTER_ASSERT(reporter, rec.fPixels == |
| (const char*)pixels + 10 * rowBytes + 10 * info.bytesPerPixel()); |
| REPORTER_ASSERT(reporter, rec.fInfo.width() == W - 10); |
| REPORTER_ASSERT(reporter, rec.fInfo.height() == H - 10); |
| REPORTER_ASSERT(reporter, rec.fX == 0); |
| REPORTER_ASSERT(reporter, rec.fY == 0); |
| } |
| |
| { |
| skiatest::ReporterContext ctx(reporter, "Trim with x, y partially outside"); |
| SkWritePixelsRec rec(info, pixels, rowBytes, 50, 50); |
| REPORTER_ASSERT(reporter, rec.trim(W, H)); |
| REPORTER_ASSERT(reporter, rec.fPixels == pixels); |
| REPORTER_ASSERT(reporter, rec.fInfo.width() == 50); |
| REPORTER_ASSERT(reporter, rec.fInfo.height() == 50); |
| REPORTER_ASSERT(reporter, rec.fX == 50); |
| REPORTER_ASSERT(reporter, rec.fY == 50); |
| } |
| |
| { |
| skiatest::ReporterContext ctx(reporter, "Trim with x, y completely outside (positive)"); |
| SkWritePixelsRec rec(info, pixels, rowBytes, 150, 150); |
| REPORTER_ASSERT(reporter, !rec.trim(W, H)); |
| } |
| |
| { |
| skiatest::ReporterContext ctx(reporter, "Trim with x, y completely outside (negative)"); |
| SkWritePixelsRec rec(info, pixels, rowBytes, -150, -150); |
| REPORTER_ASSERT(reporter, !rec.trim(W, H)); |
| } |
| |
| { |
| skiatest::ReporterContext ctx(reporter, "Explicitly trigger y_offset overflow"); |
| // arbitrarily large value that will overflow when multiplied by hugeY |
| size_t hugeRowBytes = static_cast<size_t>(INT_MAX) >> 13; |
| int hugeY = INT_MIN; |
| SkWritePixelsRec rec(info, pixels, hugeRowBytes, 0, hugeY); |
| REPORTER_ASSERT(reporter, !rec.trim(W, H)); |
| } |
| |
| { |
| if (sizeof(size_t) == 4) { |
| skiatest::ReporterContext ctx(reporter, "Explicitly trigger x_offset overflow"); |
| int hugeX = INT_MIN; |
| SkWritePixelsRec rec(info, pixels, rowBytes, hugeX, 0); |
| REPORTER_ASSERT(reporter, !rec.trim(W, H)); |
| } |
| } |
| |
| { |
| skiatest::ReporterContext ctx(reporter, "Explicitly trigger total overflow"); |
| SkWritePixelsRec rec(info, pixels, SIZE_MAX, -1, -1); |
| REPORTER_ASSERT(reporter, !rec.trim(W, H)); |
| } |
| |
| { |
| skiatest::ReporterContext ctx(reporter, "Trim with large rowBytes (initial check)"); |
| SkImageInfo hugeInfo = SkImageInfo::MakeN32Premul(1 << 30, 1); |
| SkWritePixelsRec rec(hugeInfo, pixels, 100, 0, 0); |
| REPORTER_ASSERT(reporter, !rec.trim(W, H)); |
| } |
| |
| { |
| skiatest::ReporterContext ctx(reporter, "Invalid ImageInfo (width <= 0)"); |
| SkWritePixelsRec rec(SkImageInfo::MakeN32Premul(-1, 100), pixels, rowBytes, 0, 0); |
| REPORTER_ASSERT(reporter, !rec.trim(W, H)); |
| } |
| |
| { |
| skiatest::ReporterContext ctx(reporter, "rowBytes too small"); |
| SkWritePixelsRec rec(info, pixels, info.minRowBytes() - 1, 0, 0); |
| REPORTER_ASSERT(reporter, !rec.trim(W, H)); |
| } |
| } |