* Copyright 2013 Google Inc.
*
* Use of this source code is governed by a BSD-style license that can be
* found in the LICENSE file.
*/
#include "gm/gm.h"
#include "include/core/SkBitmap.h"
#include "include/core/SkBlurTypes.h"
#include "include/core/SkCanvas.h"
#include "include/core/SkColor.h"
#include "include/core/SkFilterQuality.h"
#include "include/core/SkImage.h"
#include "include/core/SkImageInfo.h"
#include "include/core/SkMaskFilter.h"
#include "include/core/SkMatrix.h"
#include "include/core/SkPaint.h"
#include "include/core/SkPoint.h"
#include "include/core/SkRect.h"
#include "include/core/SkRefCnt.h"
#include "include/core/SkScalar.h"
#include "include/core/SkShader.h"
#include "include/core/SkSize.h"
#include "include/core/SkString.h"
#include "include/core/SkSurface.h"
#include "include/core/SkTileMode.h"
#include "include/core/SkTypes.h"
#include "include/effects/SkGradientShader.h"
#include "include/gpu/GrContextOptions.h"
#include "include/private/SkTDArray.h"
#include "src/core/SkBlurMask.h"
#include "tools/ToolUtils.h"
or image should be tested by the GM. The area outside of the rect is present to check
for bleed due to filtering/blurring. */
struct TestPixels {
enum Type {
kBitmap,
kImage
};
Type fType;
SkBitmap fBitmap;
sk_sp<SkImage> fImage;
SkIRect fRect;
};
logically where each check has as many pixels as is necessary to fill the interior. The rect
to draw is set to the checkerboard portion. */
template<typename PIXEL_TYPE>
bool make_ringed_bitmap(TestPixels* result, int width, int height,
SkColorType ct, SkAlphaType at,
PIXEL_TYPE outerRingColor, PIXEL_TYPE innerRingColor,
PIXEL_TYPE checkColor1, PIXEL_TYPE checkColor2) {
SkASSERT(0 == width % 2 && 0 == height % 2);
SkASSERT(width >= 6 && height >= 6);
result->fType = TestPixels::kBitmap;
SkImageInfo info = SkImageInfo::Make(width, height, ct, at);
size_t rowBytes = SkAlign4(info.minRowBytes());
result->fBitmap.allocPixels(info, rowBytes);
PIXEL_TYPE* scanline = (PIXEL_TYPE*)result->fBitmap.getAddr(0, 0);
for (int x = 0; x < width; ++x) {
scanline[x] = outerRingColor;
}
scanline = (PIXEL_TYPE*)result->fBitmap.getAddr(0, 1);
scanline[0] = outerRingColor;
for (int x = 1; x < width - 1; ++x) {
scanline[x] = innerRingColor;
}
scanline[width - 1] = outerRingColor;
for (int y = 2; y < height / 2; ++y) {
scanline = (PIXEL_TYPE*)result->fBitmap.getAddr(0, y);
scanline[0] = outerRingColor;
scanline[1] = innerRingColor;
for (int x = 2; x < width / 2; ++x) {
scanline[x] = checkColor1;
}
for (int x = width / 2; x < width - 2; ++x) {
scanline[x] = checkColor2;
}
scanline[width - 2] = innerRingColor;
scanline[width - 1] = outerRingColor;
}
for (int y = height / 2; y < height - 2; ++y) {
scanline = (PIXEL_TYPE*)result->fBitmap.getAddr(0, y);
scanline[0] = outerRingColor;
scanline[1] = innerRingColor;
for (int x = 2; x < width / 2; ++x) {
scanline[x] = checkColor2;
}
for (int x = width / 2; x < width - 2; ++x) {
scanline[x] = checkColor1;
}
scanline[width - 2] = innerRingColor;
scanline[width - 1] = outerRingColor;
}
scanline = (PIXEL_TYPE*)result->fBitmap.getAddr(0, height - 2);
scanline[0] = outerRingColor;
for (int x = 1; x < width - 1; ++x) {
scanline[x] = innerRingColor;
}
scanline[width - 1] = outerRingColor;
scanline = (PIXEL_TYPE*)result->fBitmap.getAddr(0, height - 1);
for (int x = 0; x < width; ++x) {
scanline[x] = outerRingColor;
}
result->fBitmap.setImmutable();
result->fRect.set(2, 2, width - 2, height - 2);
return true;
}
The inner ring is red and the outer ring is blue. */
static bool make_ringed_color_bitmap(TestPixels* result, int width, int height) {
const SkPMColor kBlue = SkPreMultiplyColor(SK_ColorBLUE);
const SkPMColor kRed = SkPreMultiplyColor(SK_ColorRED);
const SkPMColor kBlack = SkPreMultiplyColor(SK_ColorBLACK);
const SkPMColor kWhite = SkPreMultiplyColor(SK_ColorWHITE);
return make_ringed_bitmap<SkPMColor>(result, width, height, kN32_SkColorType,
kPremul_SkAlphaType, kBlue, kRed, kBlack, kWhite);
}
checker board of 3/4 and 1/2. The inner checkers are large enough to fill the interior with
the 2x2 checker grid. */
static bool make_ringed_alpha_bitmap(TestPixels* result, int width, int height) {
constexpr uint8_t kZero = 0x00;
constexpr uint8_t kHalf = 0x80;
constexpr uint8_t k3Q = 0xC0;
constexpr uint8_t kOne = 0xFF;
return make_ringed_bitmap<uint8_t>(result, width, height, kAlpha_8_SkColorType,
kPremul_SkAlphaType, kZero, kOne, k3Q, kHalf);
}
static void bmp_to_image(TestPixels* result) {
SkASSERT(TestPixels::kBitmap == result->fType);
result->fImage = SkImage::MakeFromBitmap(result->fBitmap);
SkASSERT(result->fImage);
result->fType = TestPixels::kImage;
result->fBitmap.reset();
}
bool make_ringed_color_image(TestPixels* result, int width, int height) {
if (make_ringed_color_bitmap(result, width, height)) {
bmp_to_image(result);
return true;
}
return false;
}
bool make_ringed_alpha_image(TestPixels* result, int width, int height) {
if (make_ringed_alpha_bitmap(result, width, height)) {
bmp_to_image(result);
return true;
}
return false;
}
static sk_sp<SkShader> make_shader() {
constexpr SkPoint pts[] = { {0, 0}, {20, 20} };
constexpr SkColor colors[] = { SK_ColorGREEN, SK_ColorYELLOW };
return SkGradientShader::MakeLinear(pts, colors, nullptr, 2, SkTileMode::kMirror);
}
static sk_sp<SkShader> make_null_shader() { return nullptr; }
enum BleedTest {
kUseBitmap_BleedTest,
kUseImage_BleedTest,
kUseAlphaBitmap_BleedTest,
kUseAlphaImage_BleedTest,
kUseAlphaBitmapShader_BleedTest,
kUseAlphaImageShader_BleedTest,
};
const struct {
const char* fName;
bool (*fPixelMaker)(TestPixels* result, int width, int height);
sk_sp<SkShader> (*fShaderMaker)();
} gBleedRec[] = {
{ "bleed", make_ringed_color_bitmap, make_null_shader },
{ "bleed_image", make_ringed_color_image, make_null_shader },
{ "bleed_alpha_bmp", make_ringed_alpha_bitmap, make_null_shader },
{ "bleed_alpha_image", make_ringed_alpha_image, make_null_shader },
{ "bleed_alpha_bmp_shader", make_ringed_alpha_bitmap, make_shader },
{ "bleed_alpha_image_shader", make_ringed_alpha_image, make_shader },
};
handling of :
- SrcRectConstraint(bleed vs.no - bleed)
- handling of the sub - region feature(area - of - interest) of drawBitmap*
- handling of 8888 vs. A8 (including presence of a shader in the A8 case).
In particular, we should never see the padding outside of an SkBitmap's sub - region (green for
8888, 1/4 for alpha). In some instances we can see the two outer rings outside of the area o
interest (i.e., the inner four checks) due to AA or filtering if allowed by the
SrcRectConstraint.
*/
class BleedGM : public skiagm::GM {
public:
BleedGM(BleedTest bt) : fBT(bt){}
protected:
SkString onShortName() override {
return SkString(gBleedRec[fBT].fName);
}
SkISize onISize() override {
return SkISize::Make(1200, 1080);
}
void drawPixels(SkCanvas* canvas, const TestPixels& pixels, const SkRect& src,
const SkRect& dst, const SkPaint* paint,
SkCanvas::SrcRectConstraint constraint) {
if (TestPixels::kBitmap == pixels.fType) {
canvas->drawBitmapRect(pixels.fBitmap, src, dst, paint, constraint);
} else {
canvas->drawImageRect(pixels.fImage.get(), src, dst, paint, constraint);
}
}
void drawCase1(SkCanvas* canvas, int transX, int transY, bool aa,
SkCanvas::SrcRectConstraint constraint, SkFilterQuality filter) {
SkRect src = SkRect::Make(fSmallTestPixels.fRect);
SkRect dst = SkRect::MakeXYWH(SkIntToScalar(transX), SkIntToScalar(transY),
SkIntToScalar(kBlockSize), SkIntToScalar(kBlockSize));
SkPaint paint;
paint.setFilterQuality(filter);
paint.setShader(fShader);
paint.setColor(SK_ColorBLUE);
paint.setAntiAlias(aa);
this->drawPixels(canvas, fSmallTestPixels, src, dst, &paint, constraint);
}
void drawCase2(SkCanvas* canvas, int transX, int transY, bool aa,
SkCanvas::SrcRectConstraint constraint, SkFilterQuality filter) {
SkRect src = SkRect::Make(fBigTestPixels.fRect);
SkRect dst = SkRect::MakeXYWH(SkIntToScalar(transX), SkIntToScalar(transY),
SkIntToScalar(kBlockSize), SkIntToScalar(kBlockSize));
SkPaint paint;
paint.setFilterQuality(filter);
paint.setShader(fShader);
paint.setColor(SK_ColorBLUE);
paint.setAntiAlias(aa);
this->drawPixels(canvas, fBigTestPixels, src, dst, &paint, constraint);
}
void drawCase3(SkCanvas* canvas, int transX, int transY, bool aa,
SkCanvas::SrcRectConstraint constraint, SkFilterQuality filter) {
SkRect src = SkRect::MakeXYWH(SkIntToScalar(fBigTestPixels.fRect.fLeft),
SkIntToScalar(fBigTestPixels.fRect.fTop),
fBigTestPixels.fRect.width()/2.f,
fBigTestPixels.fRect.height()/2.f);
SkRect dst = SkRect::MakeXYWH(SkIntToScalar(transX), SkIntToScalar(transY),
SkIntToScalar(kBlockSize), SkIntToScalar(kBlockSize));
SkPaint paint;
paint.setFilterQuality(filter);
paint.setShader(fShader);
paint.setColor(SK_ColorBLUE);
paint.setAntiAlias(aa);
this->drawPixels(canvas, fBigTestPixels, src, dst, &paint, constraint);
}
void drawCase4(SkCanvas* canvas, int transX, int transY, bool aa,
SkCanvas::SrcRectConstraint constraint, SkFilterQuality filter) {
SkRect src = SkRect::Make(fSmallTestPixels.fRect);
SkRect dst = SkRect::MakeXYWH(SkIntToScalar(transX), SkIntToScalar(transY),
SkIntToScalar(kBlockSize), SkIntToScalar(kBlockSize));
SkPaint paint;
paint.setFilterQuality(filter);
paint.setMaskFilter(SkMaskFilter::MakeBlur(kNormal_SkBlurStyle,
SkBlurMask::ConvertRadiusToSigma(3)));
paint.setShader(fShader);
paint.setColor(SK_ColorBLUE);
paint.setAntiAlias(aa);
this->drawPixels(canvas, fSmallTestPixels, src, dst, &paint, constraint);
}
void drawCase5(SkCanvas* canvas, int transX, int transY, bool aa,
SkCanvas::SrcRectConstraint constraint, SkFilterQuality filter) {
SkRect src = SkRect::Make(fSmallTestPixels.fRect);
SkRect dst = SkRect::MakeXYWH(SkIntToScalar(transX), SkIntToScalar(transY),
SkIntToScalar(kBlockSize), SkIntToScalar(kBlockSize));
SkPaint paint;
paint.setFilterQuality(filter);
paint.setMaskFilter(SkMaskFilter::MakeBlur(kOuter_SkBlurStyle,
SkBlurMask::ConvertRadiusToSigma(7)));
paint.setShader(fShader);
paint.setColor(SK_ColorBLUE);
paint.setAntiAlias(aa);
this->drawPixels(canvas, fSmallTestPixels, src, dst, &paint, constraint);
}
void onOnceBeforeDraw() override {
SkAssertResult(gBleedRec[fBT].fPixelMaker(&fSmallTestPixels, kSmallSize, kSmallSize));
SkAssertResult(gBleedRec[fBT].fPixelMaker(&fBigTestPixels, 2 * kMaxTileSize,
2 * kMaxTileSize));
}
void onDraw(SkCanvas* canvas) override {
fShader = gBleedRec[fBT].fShaderMaker();
canvas->clear(SK_ColorGRAY);
SkTDArray<SkMatrix> matrices;
*matrices.append() = SkMatrix::I();
SkMatrix m;
constexpr SkScalar kBottom = SkIntToScalar(kRow4Y + kBlockSize + kBlockSpacing);
m.setTranslate(0, kBottom);
m.preRotate(15.f, 0, kBottom + kBlockSpacing);
m.preScale(0.71f, 1.22f);
*matrices.append() = m;
SkPoint corners[] = {{0, 0}, { 0, kBottom }, { kWidth, kBottom }, {kWidth, 0} };
matrices[matrices.count()-1].mapPoints(corners, 4);
SkScalar y = (corners[0].fY + corners[1].fY + corners[2].fY + corners[3].fY) / 4;
SkScalar x = SkTMax(SkTMax(corners[0].fX, corners[1].fX),
SkTMax(corners[2].fX, corners[3].fX));
m.setTranslate(x, y);
m.preScale(0.2f, 0.2f);
*matrices.append() = m;
SkScalar maxX = 0;
for (int antiAlias = 0; antiAlias < 2; ++antiAlias) {
canvas->save();
canvas->translate(maxX, 0);
for (int m = 0; m < matrices.count(); ++m) {
canvas->save();
canvas->concat(matrices[m]);
bool aa = SkToBool(antiAlias);
this->drawCase1(canvas, kCol0X, kRow0Y, aa, SkCanvas::kStrict_SrcRectConstraint, kNone_SkFilterQuality);
this->drawCase2(canvas, kCol0X, kRow1Y, aa, SkCanvas::kStrict_SrcRectConstraint, kNone_SkFilterQuality);
this->drawCase3(canvas, kCol0X, kRow2Y, aa, SkCanvas::kStrict_SrcRectConstraint, kNone_SkFilterQuality);
this->drawCase4(canvas, kCol0X, kRow3Y, aa, SkCanvas::kStrict_SrcRectConstraint, kNone_SkFilterQuality);
this->drawCase5(canvas, kCol0X, kRow4Y, aa, SkCanvas::kStrict_SrcRectConstraint, kNone_SkFilterQuality);
this->drawCase1(canvas, kCol1X, kRow0Y, aa, SkCanvas::kStrict_SrcRectConstraint, kLow_SkFilterQuality);
this->drawCase2(canvas, kCol1X, kRow1Y, aa, SkCanvas::kStrict_SrcRectConstraint, kLow_SkFilterQuality);
this->drawCase3(canvas, kCol1X, kRow2Y, aa, SkCanvas::kStrict_SrcRectConstraint, kLow_SkFilterQuality);
this->drawCase4(canvas, kCol1X, kRow3Y, aa, SkCanvas::kStrict_SrcRectConstraint, kLow_SkFilterQuality);
this->drawCase5(canvas, kCol1X, kRow4Y, aa, SkCanvas::kStrict_SrcRectConstraint, kLow_SkFilterQuality);
this->drawCase1(canvas, kCol2X, kRow0Y, aa, SkCanvas::kStrict_SrcRectConstraint, kHigh_SkFilterQuality);
this->drawCase2(canvas, kCol2X, kRow1Y, aa, SkCanvas::kStrict_SrcRectConstraint, kHigh_SkFilterQuality);
this->drawCase3(canvas, kCol2X, kRow2Y, aa, SkCanvas::kStrict_SrcRectConstraint, kHigh_SkFilterQuality);
this->drawCase4(canvas, kCol2X, kRow3Y, aa, SkCanvas::kStrict_SrcRectConstraint, kHigh_SkFilterQuality);
this->drawCase5(canvas, kCol2X, kRow4Y, aa, SkCanvas::kStrict_SrcRectConstraint, kHigh_SkFilterQuality);
this->drawCase1(canvas, kCol3X, kRow0Y, aa, SkCanvas::kFast_SrcRectConstraint, kNone_SkFilterQuality);
this->drawCase2(canvas, kCol3X, kRow1Y, aa, SkCanvas::kFast_SrcRectConstraint, kNone_SkFilterQuality);
this->drawCase3(canvas, kCol3X, kRow2Y, aa, SkCanvas::kFast_SrcRectConstraint, kNone_SkFilterQuality);
this->drawCase4(canvas, kCol3X, kRow3Y, aa, SkCanvas::kFast_SrcRectConstraint, kNone_SkFilterQuality);
this->drawCase5(canvas, kCol3X, kRow4Y, aa, SkCanvas::kFast_SrcRectConstraint, kNone_SkFilterQuality);
this->drawCase1(canvas, kCol4X, kRow0Y, aa, SkCanvas::kFast_SrcRectConstraint, kLow_SkFilterQuality);
this->drawCase2(canvas, kCol4X, kRow1Y, aa, SkCanvas::kFast_SrcRectConstraint, kLow_SkFilterQuality);
this->drawCase3(canvas, kCol4X, kRow2Y, aa, SkCanvas::kFast_SrcRectConstraint, kLow_SkFilterQuality);
this->drawCase4(canvas, kCol4X, kRow3Y, aa, SkCanvas::kFast_SrcRectConstraint, kLow_SkFilterQuality);
this->drawCase5(canvas, kCol4X, kRow4Y, aa, SkCanvas::kFast_SrcRectConstraint, kLow_SkFilterQuality);
this->drawCase1(canvas, kCol5X, kRow0Y, aa, SkCanvas::kFast_SrcRectConstraint, kHigh_SkFilterQuality);
this->drawCase2(canvas, kCol5X, kRow1Y, aa, SkCanvas::kFast_SrcRectConstraint, kHigh_SkFilterQuality);
this->drawCase3(canvas, kCol5X, kRow2Y, aa, SkCanvas::kFast_SrcRectConstraint, kHigh_SkFilterQuality);
this->drawCase4(canvas, kCol5X, kRow3Y, aa, SkCanvas::kFast_SrcRectConstraint, kHigh_SkFilterQuality);
this->drawCase5(canvas, kCol5X, kRow4Y, aa, SkCanvas::kFast_SrcRectConstraint, kHigh_SkFilterQuality);
SkPoint corners[] = { { 0, 0 },{ 0, kBottom },{ kWidth, kBottom },{ kWidth, 0 } };
matrices[m].mapPoints(corners, 4);
SkScalar x = kBlockSize + SkTMax(SkTMax(corners[0].fX, corners[1].fX),
SkTMax(corners[2].fX, corners[3].fX));
maxX = SkTMax(maxX, x);
canvas->restore();
}
canvas->restore();
}
}
void modifyGrContextOptions(GrContextOptions* options) override {
options->fMaxTileSizeOverride = kMaxTileSize;
}
private:
static constexpr int kBlockSize = 70;
static constexpr int kBlockSpacing = 12;
static constexpr int kCol0X = kBlockSpacing;
static constexpr int kCol1X = 2*kBlockSpacing + kBlockSize;
static constexpr int kCol2X = 3*kBlockSpacing + 2*kBlockSize;
static constexpr int kCol3X = 4*kBlockSpacing + 3*kBlockSize;
static constexpr int kCol4X = 5*kBlockSpacing + 4*kBlockSize;
static constexpr int kCol5X = 6*kBlockSpacing + 5*kBlockSize;
static constexpr int kWidth = 7*kBlockSpacing + 6*kBlockSize;
static constexpr int kRow0Y = kBlockSpacing;
static constexpr int kRow1Y = 2*kBlockSpacing + kBlockSize;
static constexpr int kRow2Y = 3*kBlockSpacing + 2*kBlockSize;
static constexpr int kRow3Y = 4*kBlockSpacing + 3*kBlockSize;
static constexpr int kRow4Y = 5*kBlockSpacing + 4*kBlockSize;
static constexpr int kSmallSize = 6;
static constexpr int kMaxTileSize = 32;
TestPixels fBigTestPixels;
TestPixels fSmallTestPixels;
sk_sp<SkShader> fShader;
const BleedTest fBT;
typedef GM INHERITED;
};
DEF_GM( return new BleedGM(kUseBitmap_BleedTest); )
DEF_GM( return new BleedGM(kUseImage_BleedTest); )
DEF_GM( return new BleedGM(kUseAlphaBitmap_BleedTest); )
DEF_GM( return new BleedGM(kUseAlphaImage_BleedTest); )
DEF_GM( return new BleedGM(kUseAlphaBitmapShader_BleedTest); )
DEF_GM( return new BleedGM(kUseAlphaImageShader_BleedTest); )
static sk_sp<SkImage> make_image(SkCanvas* canvas, SkRect* srcR) {
const int N = 10 + 2 + 8 + 2 + 10;
SkImageInfo info = SkImageInfo::MakeN32Premul(N, N);
auto surface = ToolUtils::makeSurface(canvas, info);
SkCanvas* c = surface->getCanvas();
SkRect r = SkRect::MakeIWH(info.width(), info.height());
SkPaint paint;
paint.setColor(SK_ColorRED);
c->drawRect(r, paint);
r.inset(10, 10);
paint.setColor(SK_ColorBLUE);
c->drawRect(r, paint);
*srcR = r.makeInset(2, 2);
return surface->makeImageSnapshot();
}
DEF_SIMPLE_GM(bleed_downscale, canvas, 360, 240) {
SkRect src;
sk_sp<SkImage> img = make_image(canvas, &src);
SkPaint paint;
canvas->translate(10, 10);
const SkCanvas::SrcRectConstraint constraints[] = {
SkCanvas::kStrict_SrcRectConstraint, SkCanvas::kFast_SrcRectConstraint
};
const SkFilterQuality qualities[] = {
kNone_SkFilterQuality, kLow_SkFilterQuality, kMedium_SkFilterQuality
};
for (auto constraint : constraints) {
canvas->save();
for (auto quality : qualities) {
paint.setFilterQuality(quality);
auto surf = ToolUtils::makeSurface(canvas, SkImageInfo::MakeN32Premul(1, 1));
surf->getCanvas()->drawImageRect(img, src, SkRect::MakeWH(1, 1), &paint, constraint);
canvas->drawImageRect(surf->makeImageSnapshot(), SkRect::MakeWH(100, 100), nullptr);
canvas->translate(120, 0);
}
canvas->restore();
canvas->translate(0, 120);
}
}