/*
* Copyright 2014 Google Inc.
*
* Use of this source code is governed by a BSD-style license that can be
* found in the LICENSE file.
*/
#ifndef SkTextureCompressor_Blitter_DEFINED
#define SkTextureCompressor_Blitter_DEFINED
#include "SkTypes.h"
#include "SkBlitter.h"
namespace SkTextureCompressor {
// Ostensibly, SkBlitter::BlitRect is supposed to set a rect of pixels to full
// alpha. This becomes problematic when using compressed texture blitters, since
// the rect rarely falls along block boundaries. The proper way to handle this is
// to update the compressed encoding of a block by resetting the proper parameters
// (and even recompressing the block) where a rect falls inbetween block boundaries.
// PEDANTIC_BLIT_RECT attempts to do this by requiring the struct passed to
// SkTCompressedAlphaBlitter to implement an UpdateBlock function call.
//
// However, the way that BlitRect gets used almost exclusively is to bracket inverse
// fills for paths. In other words, the top few rows and bottom few rows of a path
// that's getting inverse filled are called using blitRect. The rest are called using
// the standard blitAntiH. As a result, we can just call blitAntiH with a faux RLE
// of full alpha values, and then check in our flush() call that we don't run off the
// edge of the buffer. This is why we do not need this flag to be turned on.
//
// NOTE: This code is unfinished, but is inteded as a starting point if an when
// bugs are introduced from the existing code.
#define PEDANTIC_BLIT_RECT 0
// This class implements a blitter that blits directly into a buffer that will
// be used as an compressed alpha texture. We compute this buffer by
// buffering scan lines and then outputting them all at once. The number of
// scan lines buffered is controlled by kBlockSize
//
// The CompressorType is a struct with a bunch of static methods that provides
// the specialized compression functionality of the blitter. A complete CompressorType
// will implement the following static functions;
//
// struct CompressorType {
// // The function used to compress an A8 block. The layout of the
// // block is also expected to be in column-major order.
// static void CompressA8Vertical(uint8_t* dst, const uint8_t block[]);
//
// // The function used to compress an A8 block. The layout of the
// // block is also expected to be in row-major order.
// static void CompressA8Horizontal(uint8_t* dst, const uint8_t* src, int srcRowBytes);
//
#if PEDANTIC_BLIT_RECT
// // The function used to update an already compressed block. This will
// // most likely be implementation dependent. The mask variable will have
// // 0xFF in positions where the block should be updated and 0 in positions
// // where it shouldn't. src contains an uncompressed buffer of pixels.
// static void UpdateBlock(uint8_t* dst, const uint8_t* src, int srcRowBytes,
// const uint8_t* mask);
#endif
// };
template<int BlockDim, int EncodedBlockSize, typename CompressorType>
class SkTCompressedAlphaBlitter : public SkBlitter {
public:
SkTCompressedAlphaBlitter(int width, int height, void *compressedBuffer)
// 0x7FFE is one minus the largest positive 16-bit int. We use it for
// debugging to make sure that we're properly setting the nextX distance
// in flushRuns().
#ifdef SK_DEBUG
: fCalledOnceWithNonzeroY(false)
, fBlitMaskCalled(false),
#else
:
#endif
kLongestRun(0x7FFE), kZeroAlpha(0)
, fNextRun(0)
, fWidth(width)
, fHeight(height)
, fBuffer(compressedBuffer)
{
SkASSERT((width % BlockDim) == 0);
SkASSERT((height % BlockDim) == 0);
}
virtual ~SkTCompressedAlphaBlitter() { this->flushRuns(); }
// Blit a horizontal run of one or more pixels.
void blitH(int x, int y, int width) override {
// This function is intended to be called from any standard RGB
// buffer, so we should never encounter it. However, if some code
// path does end up here, then this needs to be investigated.
SkFAIL("Not implemented!");
}
// Blit a horizontal run of antialiased pixels; runs[] is a *sparse*
// zero-terminated run-length encoding of spans of constant alpha values.
void blitAntiH(int x, int y,
const SkAlpha antialias[],
const int16_t runs[]) override {
SkASSERT(0 == x);
// Make sure that the new row to blit is either the first
// row that we're blitting, or it's exactly the next scan row
// since the last row that we blit. This is to ensure that when
// we go to flush the runs, that they are all the same four
// runs.
if (fNextRun > 0 &&
((x != fBufferedRuns[fNextRun-1].fX) ||
(y-1 != fBufferedRuns[fNextRun-1].fY))) {
this->flushRuns();
}
// Align the rows to a block boundary. If we receive rows that
// are not on a block boundary, then fill in the preceding runs
// with zeros. We do this by producing a single RLE that says
// that we have 0x7FFE pixels of zero (0x7FFE = 32766).
const int row = BlockDim * (y / BlockDim);
while ((row + fNextRun) < y) {
fBufferedRuns[fNextRun].fAlphas = &kZeroAlpha;
fBufferedRuns[fNextRun].fRuns = &kLongestRun;
fBufferedRuns[fNextRun].fX = 0;
fBufferedRuns[fNextRun].fY = row + fNextRun;
++fNextRun;
}
// Make sure that our assumptions aren't violated...
SkASSERT(fNextRun == (y % BlockDim));
SkASSERT(fNextRun == 0 || fBufferedRuns[fNextRun - 1].fY < y);
// Set the values of the next run
fBufferedRuns[fNextRun].fAlphas = antialias;
fBufferedRuns[fNextRun].fRuns = runs;
fBufferedRuns[fNextRun].fX = x;
fBufferedRuns[fNextRun].fY = y;
// If we've output a block of scanlines in a row that don't violate our
// assumptions, then it's time to flush them...
if (BlockDim == ++fNextRun) {
this->flushRuns();
}
}
// Blit a vertical run of pixels with a constant alpha value.
void blitV(int x, int y, int height, SkAlpha alpha) override {
// This function is currently not implemented. It is not explicitly
// required by the contract, but if at some time a code path runs into
// this function (which is entirely possible), it needs to be implemented.
//
// TODO (krajcevski):
// This function will be most easily implemented in one of two ways:
// 1. Buffer each vertical column value and then construct a list
// of alpha values and output all of the blocks at once. This only
// requires a write to the compressed buffer
// 2. Replace the indices of each block with the proper indices based
// on the alpha value. This requires a read and write of the compressed
// buffer, but much less overhead.
SkFAIL("Not implemented!");
}
// Blit a solid rectangle one or more pixels wide. It's assumed that blitRect
// is called as a way to bracket blitAntiH where above and below the path the
// called path just needs a solid rectangle to fill in the mask.
#ifdef SK_DEBUG
bool fCalledOnceWithNonzeroY;
#endif
void blitRect(int x, int y, int width, int height) override {
// Assumptions:
SkASSERT(0 == x);
SkASSERT(width <= fWidth);
// Make sure that we're only ever bracketing calls to blitAntiH.
SkASSERT((0 == y) || (!fCalledOnceWithNonzeroY && (fCalledOnceWithNonzeroY = true)));
#if !(PEDANTIC_BLIT_RECT)
for (int i = 0; i < height; ++i) {
const SkAlpha kFullAlpha = 0xFF;
this->blitAntiH(x, y+i, &kFullAlpha, &kLongestRun);
}
#else
const int startBlockX = (x / BlockDim) * BlockDim;
const int startBlockY = (y / BlockDim) * BlockDim;
const int endBlockX = ((x + width) / BlockDim) * BlockDim;
const int endBlockY = ((y + height) / BlockDim) * BlockDim;
// If start and end are the same, then we only need to update a single block...
if (startBlockY == endBlockY && startBlockX == endBlockX) {
uint8_t mask[BlockDim*BlockDim];
memset(mask, 0, sizeof(mask));
const int xoff = x - startBlockX;
SkASSERT((xoff + width) <= BlockDim);
const int yoff = y - startBlockY;
SkASSERT((yoff + height) <= BlockDim);
for (int j = 0; j < height; ++j) {
memset(mask + (j + yoff)*BlockDim + xoff, 0xFF, width);
}
uint8_t* dst = this->getBlock(startBlockX, startBlockY);
CompressorType::UpdateBlock(dst, mask, BlockDim, mask);
// If start and end are the same in the y dimension, then we can freely update an
// entire row of blocks...
} else if (startBlockY == endBlockY) {
this->updateBlockRow(x, y, width, height, startBlockY, startBlockX, endBlockX);
// Similarly, if the start and end are in the same column, then we can just update
// an entire column of blocks...
} else if (startBlockX == endBlockX) {
this->updateBlockCol(x, y, width, height, startBlockX, startBlockY, endBlockY);
// Otherwise, the rect spans a non-trivial region of blocks, and we have to construct
// a kind of 9-patch to update each of the pieces of the rect. The top and bottom
// rows are updated using updateBlockRow, and the left and right columns are updated
// using updateBlockColumn. Anything in the middle is simply memset to an opaque block
// encoding.
} else {
const int innerStartBlockX = startBlockX + BlockDim;
const int innerStartBlockY = startBlockY + BlockDim;
// Blit top row
const int topRowHeight = innerStartBlockY - y;
this->updateBlockRow(x, y, width, topRowHeight, startBlockY,
startBlockX, endBlockX);
// Advance y
y += topRowHeight;
height -= topRowHeight;
// Blit middle
if (endBlockY > innerStartBlockY) {
// Update left row
this->updateBlockCol(x, y, innerStartBlockX - x, endBlockY, startBlockY,
startBlockX, innerStartBlockX);
// Update the middle with an opaque encoding...
uint8_t mask[BlockDim*BlockDim];
memset(mask, 0xFF, sizeof(mask));
uint8_t opaqueEncoding[EncodedBlockSize];
CompressorType::CompressA8Horizontal(opaqueEncoding, mask, BlockDim);
for (int j = innerStartBlockY; j < endBlockY; j += BlockDim) {
uint8_t* opaqueDst = this->getBlock(innerStartBlockX, j);
for (int i = innerStartBlockX; i < endBlockX; i += BlockDim) {
memcpy(opaqueDst, opaqueEncoding, EncodedBlockSize);
opaqueDst += EncodedBlockSize;
}
}
// If we need to update the right column, do that too
if (x + width > endBlockX) {
this->updateBlockCol(endBlockX, y, x + width - endBlockX, endBlockY,
endBlockX, innerStartBlockY, endBlockY);
}
// Advance y
height = y + height - endBlockY;
y = endBlockY;
}
// If we need to update the last row, then do that, too.
if (height > 0) {
this->updateBlockRow(x, y, width, height, endBlockY,
startBlockX, endBlockX);
}
}
#endif
}
// Blit a rectangle with one alpha-blended column on the left,
// width (zero or more) opaque pixels, and one alpha-blended column
// on the right. The result will always be at least two pixels wide.
void blitAntiRect(int x, int y, int width, int height,
SkAlpha leftAlpha, SkAlpha rightAlpha) override {
// This function is currently not implemented. It is not explicitly
// required by the contract, but if at some time a code path runs into
// this function (which is entirely possible), it needs to be implemented.
//
// TODO (krajcevski):
// This function will be most easily implemented as follows:
// 1. If width/height are smaller than a block, then update the
// indices of the affected blocks.
// 2. If width/height are larger than a block, then construct a 9-patch
// of block encodings that represent the rectangle, and write them
// to the compressed buffer as necessary. Whether or not the blocks
// are overwritten by zeros or just their indices are updated is up
// to debate.
SkFAIL("Not implemented!");
}
// Blit a pattern of pixels defined by a rectangle-clipped mask; We make an
// assumption here that if this function gets called, then it will replace all
// of the compressed texture blocks that it touches. Hence, two separate calls
// to blitMask that have clips next to one another will cause artifacts. Most
// of the time, however, this function gets called because constructing the mask
// was faster than constructing the RLE for blitAntiH, and this function will
// only be called once.
#ifdef SK_DEBUG
bool fBlitMaskCalled;
#endif
void blitMask(const SkMask& mask, const SkIRect& clip) override {
// Assumptions:
SkASSERT(!fBlitMaskCalled);
SkDEBUGCODE(fBlitMaskCalled = true);
SkASSERT(SkMask::kA8_Format == mask.fFormat);
SkASSERT(mask.fBounds.contains(clip));
// Start from largest block boundary less than the clip boundaries.
const int startI = BlockDim * (clip.left() / BlockDim);
const int startJ = BlockDim * (clip.top() / BlockDim);
for (int j = startJ; j < clip.bottom(); j += BlockDim) {
// Get the destination for this block row
uint8_t* dst = this->getBlock(startI, j);
for (int i = startI; i < clip.right(); i += BlockDim) {
// At this point, the block should intersect the clip.
SkASSERT(SkIRect::IntersectsNoEmptyCheck(
SkIRect::MakeXYWH(i, j, BlockDim, BlockDim), clip));
// Do we need to pad it?
if (i < clip.left() || j < clip.top() ||
i + BlockDim > clip.right() || j + BlockDim > clip.bottom()) {
uint8_t block[BlockDim*BlockDim];
memset(block, 0, sizeof(block));
const int startX = SkMax32(i, clip.left());
const int startY = SkMax32(j, clip.top());
const int endX = SkMin32(i + BlockDim, clip.right());
const int endY = SkMin32(j + BlockDim, clip.bottom());
for (int y = startY; y < endY; ++y) {
const int col = startX - i;
const int row = y - j;
const int valsWide = endX - startX;
SkASSERT(valsWide <= BlockDim);
SkASSERT(0 <= col && col < BlockDim);
SkASSERT(0 <= row && row < BlockDim);
memcpy(block + row*BlockDim + col,
mask.getAddr8(startX, j + row), valsWide);
}
CompressorType::CompressA8Horizontal(dst, block, BlockDim);
} else {
// Otherwise, just compress it.
uint8_t*const src = mask.getAddr8(i, j);
const uint32_t rb = mask.fRowBytes;
CompressorType::CompressA8Horizontal(dst, src, rb);
}
dst += EncodedBlockSize;
}
}
}
// If the blitter just sets a single value for each pixel, return the
// bitmap it draws into, and assign value. If not, return NULL and ignore
// the value parameter.
const SkBitmap* justAnOpaqueColor(uint32_t* value) override {
return NULL;
}
/**
* Compressed texture blitters only really work correctly if they get
* BlockDim rows at a time. That being said, this blitter tries it's best
* to preserve semantics if blitAntiH doesn't get called in too many
* weird ways...
*/
int requestRowsPreserved() const override { return BlockDim; }
private:
static const int kPixelsPerBlock = BlockDim * BlockDim;
// The longest possible run of pixels that this blitter will receive.
// This is initialized in the constructor to 0x7FFE, which is one less
// than the largest positive 16-bit integer. We make sure that it's one
// less for debugging purposes. We also don't make this variable static
// in order to make sure that we can construct a valid pointer to it.
const int16_t kLongestRun;
// Usually used in conjunction with kLongestRun. This is initialized to
// zero.
const SkAlpha kZeroAlpha;
// This is the information that we buffer whenever we're asked to blit
// a row with this blitter.
struct BufferedRun {
const SkAlpha* fAlphas;
const int16_t* fRuns;
int fX, fY;
} fBufferedRuns[BlockDim];
// The next row [0, BlockDim) that we need to blit.
int fNextRun;
// The width and height of the image that we're blitting
const int fWidth;
const int fHeight;
// The compressed buffer that we're blitting into. It is assumed that the buffer
// is large enough to store a compressed image of size fWidth*fHeight.
void* const fBuffer;
// Various utility functions
int blocksWide() const { return fWidth / BlockDim; }
int blocksTall() const { return fHeight / BlockDim; }
int totalBlocks() const { return (fWidth * fHeight) / kPixelsPerBlock; }
// Returns the block index for the block containing pixel (x, y). Block
// indices start at zero and proceed in raster order.
int getBlockOffset(int x, int y) const {
SkASSERT(x < fWidth);
SkASSERT(y < fHeight);
const int blockCol = x / BlockDim;
const int blockRow = y / BlockDim;
return blockRow * this->blocksWide() + blockCol;
}
// Returns a pointer to the block containing pixel (x, y)
uint8_t *getBlock(int x, int y) const {
uint8_t* ptr = reinterpret_cast<uint8_t*>(fBuffer);
return ptr + EncodedBlockSize*this->getBlockOffset(x, y);
}
// Updates the block whose columns are stored in block. curAlphai is expected
// to store the alpha values that will be placed within each of the columns in
// the range [col, col+colsLeft).
typedef uint32_t Column[BlockDim/4];
typedef uint32_t Block[BlockDim][BlockDim/4];
inline void updateBlockColumns(Block block, const int col,
const int colsLeft, const Column curAlphai) {
SkASSERT(block);
SkASSERT(col + colsLeft <= BlockDim);
for (int i = col; i < (col + colsLeft); ++i) {
memcpy(block[i], curAlphai, sizeof(Column));
}
}
// The following function writes the buffered runs to compressed blocks.
// If fNextRun < BlockDim, then we fill the runs that we haven't buffered with
// the constant zero buffer.
void flushRuns() {
// If we don't have any runs, then just return.
if (0 == fNextRun) {
return;
}
#ifndef NDEBUG
// Make sure that if we have any runs, they all match
for (int i = 1; i < fNextRun; ++i) {
SkASSERT(fBufferedRuns[i].fY == fBufferedRuns[i-1].fY + 1);
SkASSERT(fBufferedRuns[i].fX == fBufferedRuns[i-1].fX);
}
#endif
// If we don't have as many runs as we have rows, fill in the remaining
// runs with constant zeros.
for (int i = fNextRun; i < BlockDim; ++i) {
fBufferedRuns[i].fY = fBufferedRuns[0].fY + i;
fBufferedRuns[i].fX = fBufferedRuns[0].fX;
fBufferedRuns[i].fAlphas = &kZeroAlpha;
fBufferedRuns[i].fRuns = &kLongestRun;
}
// Make sure that our assumptions aren't violated.
SkASSERT(fNextRun > 0 && fNextRun <= BlockDim);
SkASSERT((fBufferedRuns[0].fY % BlockDim) == 0);
// The following logic walks BlockDim rows at a time and outputs compressed
// blocks to the buffer passed into the constructor.
// We do the following:
//
// c1 c2 c3 c4
// -----------------------------------------------------------------------
// ... | | | | | ----> fBufferedRuns[0]
// -----------------------------------------------------------------------
// ... | | | | | ----> fBufferedRuns[1]
// -----------------------------------------------------------------------
// ... | | | | | ----> fBufferedRuns[2]
// -----------------------------------------------------------------------
// ... | | | | | ----> fBufferedRuns[3]
// -----------------------------------------------------------------------
//
// curX -- the macro X value that we've gotten to.
// c[BlockDim] -- the buffers that represent the columns of the current block
// that we're operating on
// curAlphaColumn -- buffer containing the column of alpha values from fBufferedRuns.
// nextX -- for each run, the next point at which we need to update curAlphaColumn
// after the value of curX.
// finalX -- the minimum of all the nextX values.
//
// curX advances to finalX outputting any blocks that it passes along
// the way. Since finalX will not change when we reach the end of a
// run, the termination criteria will be whenever curX == finalX at the
// end of a loop.
// Setup:
Block block;
sk_bzero(block, sizeof(block));
Column curAlphaColumn;
sk_bzero(curAlphaColumn, sizeof(curAlphaColumn));
SkAlpha *curAlpha = reinterpret_cast<SkAlpha*>(&curAlphaColumn);
int nextX[BlockDim];
for (int i = 0; i < BlockDim; ++i) {
nextX[i] = 0x7FFFFF;
}
uint8_t* outPtr = this->getBlock(fBufferedRuns[0].fX, fBufferedRuns[0].fY);
// Populate the first set of runs and figure out how far we need to
// advance on the first step
int curX = 0;
int finalX = 0xFFFFF;
for (int i = 0; i < BlockDim; ++i) {
nextX[i] = *(fBufferedRuns[i].fRuns);
curAlpha[i] = *(fBufferedRuns[i].fAlphas);
finalX = SkMin32(nextX[i], finalX);
}
// Make sure that we have a valid right-bound X value
SkASSERT(finalX < 0xFFFFF);
// If the finalX is the longest run, then just blit until we have
// width...
if (kLongestRun == finalX) {
finalX = fWidth;
}
// Run the blitter...
while (curX != finalX) {
SkASSERT(finalX >= curX);
// Do we need to populate the rest of the block?
if ((finalX - (BlockDim*(curX / BlockDim))) >= BlockDim) {
const int col = curX % BlockDim;
const int colsLeft = BlockDim - col;
SkASSERT(curX + colsLeft <= finalX);
this->updateBlockColumns(block, col, colsLeft, curAlphaColumn);
// Write this block
CompressorType::CompressA8Vertical(outPtr, reinterpret_cast<uint8_t*>(block));
outPtr += EncodedBlockSize;
curX += colsLeft;
}
// If we can advance even further, then just keep memsetting the block
if ((finalX - curX) >= BlockDim) {
SkASSERT((curX % BlockDim) == 0);
const int col = 0;
const int colsLeft = BlockDim;
this->updateBlockColumns(block, col, colsLeft, curAlphaColumn);
// While we can keep advancing, just keep writing the block.
uint8_t lastBlock[EncodedBlockSize];
CompressorType::CompressA8Vertical(lastBlock, reinterpret_cast<uint8_t*>(block));
while((finalX - curX) >= BlockDim) {
memcpy(outPtr, lastBlock, EncodedBlockSize);
outPtr += EncodedBlockSize;
curX += BlockDim;
}
}
// If we haven't advanced within the block then do so.
if (curX < finalX) {
const int col = curX % BlockDim;
const int colsLeft = finalX - curX;
this->updateBlockColumns(block, col, colsLeft, curAlphaColumn);
curX += colsLeft;
}
SkASSERT(curX == finalX);
// Figure out what the next advancement is...
if (finalX < fWidth) {
for (int i = 0; i < BlockDim; ++i) {
if (nextX[i] == finalX) {
const int16_t run = *(fBufferedRuns[i].fRuns);
fBufferedRuns[i].fRuns += run;
fBufferedRuns[i].fAlphas += run;
curAlpha[i] = *(fBufferedRuns[i].fAlphas);
nextX[i] += *(fBufferedRuns[i].fRuns);
}
}
finalX = 0xFFFFF;
for (int i = 0; i < BlockDim; ++i) {
finalX = SkMin32(nextX[i], finalX);
}
} else {
curX = finalX;
}
}
// If we didn't land on a block boundary, output the block...
if ((curX % BlockDim) > 0) {
#ifdef SK_DEBUG
for (int i = 0; i < BlockDim; ++i) {
SkASSERT(nextX[i] == kLongestRun || nextX[i] == curX);
}
#endif
const int col = curX % BlockDim;
const int colsLeft = BlockDim - col;
memset(curAlphaColumn, 0, sizeof(curAlphaColumn));
this->updateBlockColumns(block, col, colsLeft, curAlphaColumn);
CompressorType::CompressA8Vertical(outPtr, reinterpret_cast<uint8_t*>(block));
}
fNextRun = 0;
}
#if PEDANTIC_BLIT_RECT
void updateBlockRow(int x, int y, int width, int height,
int blockRow, int startBlockX, int endBlockX) {
if (0 == width || 0 == height || startBlockX == endBlockX) {
return;
}
uint8_t* dst = this->getBlock(startBlockX, BlockDim * (y / BlockDim));
// One horizontal strip to update
uint8_t mask[BlockDim*BlockDim];
memset(mask, 0, sizeof(mask));
// Update the left cap
int blockX = startBlockX;
const int yoff = y - blockRow;
for (int j = 0; j < height; ++j) {
const int xoff = x - blockX;
memset(mask + (j + yoff)*BlockDim + xoff, 0xFF, BlockDim - xoff);
}
CompressorType::UpdateBlock(dst, mask, BlockDim, mask);
dst += EncodedBlockSize;
blockX += BlockDim;
// Update the middle
if (blockX < endBlockX) {
for (int j = 0; j < height; ++j) {
memset(mask + (j + yoff)*BlockDim, 0xFF, BlockDim);
}
while (blockX < endBlockX) {
CompressorType::UpdateBlock(dst, mask, BlockDim, mask);
dst += EncodedBlockSize;
blockX += BlockDim;
}
}
SkASSERT(endBlockX == blockX);
// Update the right cap (if we need to)
if (x + width > endBlockX) {
memset(mask, 0, sizeof(mask));
for (int j = 0; j < height; ++j) {
const int xoff = (x+width-blockX);
memset(mask + (j+yoff)*BlockDim, 0xFF, xoff);
}
CompressorType::UpdateBlock(dst, mask, BlockDim, mask);
}
}
void updateBlockCol(int x, int y, int width, int height,
int blockCol, int startBlockY, int endBlockY) {
if (0 == width || 0 == height || startBlockY == endBlockY) {
return;
}
// One vertical strip to update
uint8_t mask[BlockDim*BlockDim];
memset(mask, 0, sizeof(mask));
const int maskX0 = x - blockCol;
const int maskWidth = maskX0 + width;
SkASSERT(maskWidth <= BlockDim);
// Update the top cap
int blockY = startBlockY;
for (int j = (y - blockY); j < BlockDim; ++j) {
memset(mask + maskX0 + j*BlockDim, 0xFF, maskWidth);
}
CompressorType::UpdateBlock(this->getBlock(blockCol, blockY), mask, BlockDim, mask);
blockY += BlockDim;
// Update middle
if (blockY < endBlockY) {
for (int j = 0; j < BlockDim; ++j) {
memset(mask + maskX0 + j*BlockDim, 0xFF, maskWidth);
}
while (blockY < endBlockY) {
CompressorType::UpdateBlock(this->getBlock(blockCol, blockY),
mask, BlockDim, mask);
blockY += BlockDim;
}
}
SkASSERT(endBlockY == blockY);
// Update bottom
if (y + height > endBlockY) {
for (int j = y+height; j < endBlockY + BlockDim; ++j) {
memset(mask + (j-endBlockY)*BlockDim, 0, BlockDim);
}
CompressorType::UpdateBlock(this->getBlock(blockCol, blockY),
mask, BlockDim, mask);
}
}
#endif // PEDANTIC_BLIT_RECT
};
} // namespace SkTextureCompressor
#endif // SkTextureCompressor_Blitter_DEFINED