void drawXFormPixelWithAlpha(const uint32_t i, const uint32_t j, const GRect &srcRect, const GIRect &dstRect, const GBitmap &src, const GBitmap &dst, const uint8_t alpha, const BlendFunc blend = blend_srcover) { GVec3f ctxPt(static_cast<float>(dstRect.fLeft + i) + 0.5f, static_cast<float>(dstRect.fTop + j) + 0.5f, 1.0f); ctxPt = m_CTMInv * ctxPt; if(ContainsPoint(srcRect, ctxPt[0], ctxPt[1])) { uint32_t x = static_cast<uint32_t>(ctxPt[0] - srcRect.fLeft); uint32_t y = static_cast<uint32_t>(ctxPt[1] - srcRect.fTop); GPixel *srcRow = GetRow(src, y); GPixel *dstRow = GetRow(dst, j+dstRect.fTop) + dstRect.fLeft; uint32_t srcA = fixed_multiply(GPixel_GetA(srcRow[x]), alpha); uint32_t srcR = fixed_multiply(GPixel_GetR(srcRow[x]), alpha); uint32_t srcG = fixed_multiply(GPixel_GetG(srcRow[x]), alpha); uint32_t srcB = fixed_multiply(GPixel_GetB(srcRow[x]), alpha); GPixel src = GPixel_PackARGB(srcA, srcR, srcG, srcB); dstRow[i] = blend(dstRow[i], src); } }
static GPixel blend_srcover(GPixel dst, GPixel src) { uint32_t srcA = GPixel_GetA(src); if(srcA == 255) { return src; } uint32_t srcR = GPixel_GetR(src); uint32_t srcG = GPixel_GetG(src); uint32_t srcB = GPixel_GetB(src); uint32_t dstA = GPixel_GetA(dst); uint32_t dstR = GPixel_GetR(dst); uint32_t dstG = GPixel_GetG(dst); uint32_t dstB = GPixel_GetB(dst); return GPixel_PackARGB(srcA + fixed_multiply(dstA, 255 - srcA), srcR + fixed_multiply(dstR, 255 - srcA), srcG + fixed_multiply(dstG, 255 - srcA), srcB + fixed_multiply(dstB, 255 - srcA)); }
static void srcover_row(GPixel row[], int count, GPixel src) { unsigned sa = GPixel_GetA(src); unsigned sr = GPixel_GetR(src); unsigned sg = GPixel_GetG(src); unsigned sb = GPixel_GetB(src); unsigned isa = 255 - sa; for (int i = 0; i < count; ++i) { GPixel dst = row[i]; unsigned da = GPixel_GetA(dst); unsigned dr = GPixel_GetR(dst); unsigned dg = GPixel_GetG(dst); unsigned db = GPixel_GetB(dst); unsigned a = sa + GDiv255(isa * da); unsigned r = sr + GDiv255(isa * dr); unsigned g = sg + GDiv255(isa * dg); unsigned b = sb + GDiv255(isa * db); row[i] = GPixel_PackARGB(a, r, g, b); } }
static void convertToPNG(const GPixel src[], int width, char dst[]) { for (int i = 0; i < width; i++) { GPixel c = *src++; int a = GPixel_GetA(c); int r = GPixel_GetR(c); int g = GPixel_GetG(c); int b = GPixel_GetB(c); // PNG requires unpremultiplied, but GPixel is premultiplied if (0 != a && 255 != a) { r = r * 255 / a; g = g * 255 / a; b = b * 255 / a; } *dst++ = r; *dst++ = g; *dst++ = b; *dst++ = a; } }
/** * Fill the rectangle with color, using SRC_OVER porter-duff mode. * * The affected pixels are those whose centers are "contained" inside the rectangle: * e.g. contained == center > min_edge && center <= max_edge * * Any area in the rectangle that is outside of the bounds of the canvas is ignored. */ void fillRect(const GRect& rect, const GColor& color) { // Get dimensions of rectangle int le = GRoundToInt(rect.left()); int to = GRoundToInt(rect.top()); int ri = GRoundToInt(rect.right()); int bo = GRoundToInt(rect.bottom()); // Fix rectangle boundaries if (le < 0) {le = 0;} if (to < 0) {to = 0;} if (ri > draw.width()) {ri = draw.width();} if (bo > draw.height()) {bo = draw.height();} // Don't draw if rectangle is of zero size if ((le < 0 && ri < 0) || (le > draw.width() && ri > draw.width())) { return; } if ((to < 0 && bo < 0) || (to > draw.height() && bo > draw.height())) { return; } if ((to == bo) || (le == ri)) { return; } // If alpha value is 0, end method if (color.fA <= 0) { return; } // Prepare color to place into bitmap GColor c = color.pinToUnit(); unsigned a = (int)(c.fA * 255.99999); unsigned r = (int)(c.fR * c.fA * 255.99999); unsigned g = (int)(c.fG * c.fA * 255.99999); unsigned b = (int)(c.fB * c.fA * 255.99999); // Fill desired pixels GPixel* dst = draw.fPixels; dst = (GPixel*)((char*)dst + (int)to * draw.rowBytes()); for (int y = to; y < bo; ++y) { for (int x = le; x < ri; ++x) { if (GPixel_GetA(*draw.getAddr(x, y)) > 0) { // blend float sA = c.fA + ((GPixel_GetA(*draw.getAddr(x, y)) / 255.99999) * (1.0f - c.fA)); float sR = (c.fR * c.fA) + ((GPixel_GetR(*draw.getAddr(x, y)) / 255.99999) * (1.0f - c.fA)); float sG = (c.fG * c.fA) + ((GPixel_GetG(*draw.getAddr(x, y)) / 255.99999) * (1.0f - c.fA)); float sB = (c.fB * c.fA) + ((GPixel_GetB(*draw.getAddr(x, y)) / 255.99999) * (1.0f - c.fA)); unsigned nA = (int)(sA * 255.99999); unsigned nR = (int)(sR * 255.99999); unsigned nG = (int)(sG * 255.99999); unsigned nB = (int)(sB * 255.99999); dst[x] = GPixel_PackARGB(nA, nR, nG, nB); } else { // Fill color unblended dst[x] = GPixel_PackARGB(a, r, g, b); } } dst = (GPixel*)((char*)dst + draw.rowBytes()); } }
/** * Fill the convex polygon with the color, following the same "containment" rule as * rectangles. * * Any area in the polygon that is outside of the bounds of the canvas is ignored. * * If the color's alpha is < 1, blend it using SRCOVER blend mode. */ void fillConvexPolygon(const GPoint* po, int count, const GColor& color) { // Consider points // int count = # of points // Do nothing if less than 3 points (i.e. not enough points to make a polygon) if (count < 3) { return; } // Adjust points according to CTM // Array to hold adjusted points GPoint* p = new GPoint[count]; // Loop to change each point for (int i = 0; i < count; i++) { float px; float py; px = (CTM[0] * po[i].x()) + (CTM[1] * po[i].y()) + (CTM[2]); py = (CTM[3] * po[i].x()) + (CTM[4] * po[i].y()) + (CTM[5]); // Set new x and y p[i].set(px, py); } // Create edges Edge tmpE; std::vector<Edge> eArr; for (int i = 0; i < count; i++) { if (i == (count - 1)) { if (GFloorToInt(p[i].x() + 0.5) == GFloorToInt(p[0].x() + 0.5)) { // vertical line tmpE.top = GFloorToInt(std::min(p[i].y(),p[0].y()) + 0.5); tmpE.bottom = GFloorToInt(std::max(p[i].y(),p[0].y()) + 0.5); tmpE.m = 0; tmpE.b = 0; tmpE.v = GFloorToInt(p[i].x() + 0.5); tmpE.x = tmpE.v; } else if (GFloorToInt(p[i].y() + 0.5) == GFloorToInt(p[0].y() + 0.5)) { // horizontal line tmpE.top = 0; tmpE.bottom = 0; tmpE.m = 0; tmpE.b = 0; tmpE.v = 0; tmpE.x = 0; } else { tmpE.top = GFloorToInt(std::min(p[i].y(),p[0].y()) + 0.5); tmpE.bottom = GFloorToInt(std::max(p[i].y(),p[0].y()) + 0.5); tmpE.m = ((p[0].x()-p[i].x())/(p[0].y()-p[i].y())); tmpE.b = (p[i].x()-(tmpE.m * p[i].y())); tmpE.v = 0; tmpE.x = GFloorToInt(std::min(p[i].x(), p[0].x()) + 0.5); } } else { if (GFloorToInt(p[i].x() + 0.5) == GFloorToInt(p[i+1].x() + 0.5)) { // vertical line tmpE.top = GFloorToInt(std::min(p[i].y(),p[i+1].y()) + 0.5); tmpE.bottom = GFloorToInt(std::max(p[i].y(),p[i+1].y()) + 0.5); tmpE.m = 0; tmpE.b = 0; tmpE.v = GFloorToInt(p[i].x() + 0.5); tmpE.x = tmpE.v; } else if (GFloorToInt(p[i].y() + 0.5) == GFloorToInt(p[i+1].y() + 0.5)) { // horizontal line tmpE.top = 0; tmpE.bottom = 0; tmpE.m = 0; tmpE.b = 0; tmpE.v = 0; tmpE.x = 0; } else { tmpE.top = GFloorToInt(std::min(p[i].y(),p[i+1].y()) + 0.5); tmpE.bottom = GFloorToInt(std::max(p[i].y(),p[i+1].y()) + 0.5); tmpE.m = ((p[i+1].x()-p[i].x())/(p[i+1].y()-p[i].y())); tmpE.b = (p[i].x()-(tmpE.m * p[i].y())); tmpE.v = 0; tmpE.x = GFloorToInt(std::min(p[i].x(), p[i+1].x()) + 0.5); } } if (tmpE.top != tmpE.bottom) { eArr.push_back(tmpE); } } // Sort edges std::sort(eArr.begin(), eArr.end(), sortEdges); // Prepare color to fill GColor c = color.pinToUnit(); unsigned a = (int)(c.fA * 255.99999); unsigned r = (int)(c.fR * c.fA * 255.99999); unsigned g = (int)(c.fG * c.fA * 255.99999); unsigned b = (int)(c.fB * c.fA * 255.99999); // Find starting y pixel int startY = eArr[0].top; // Find ending y pixel int endY = eArr.back().bottom; // Fill desired pixels // Set indexes for left and right edges at beginning int index; int currL; int currR; index = 0; currL = index; currR = index + 1; index += 2; GPixel* d = draw.fPixels; d = (GPixel*)((char*)d + (int)startY * draw.rowBytes()); for (int y = startY; y < endY; ++y) { for (int x = lrBound(eArr, currL, y); x < lrBound(eArr, currR, y); ++x) { // blend if alpha isn't 255 // if pixel is out of bounds, skip pixel if ((x >= 0) && (x < draw.width()) && (y >= 0) && (y < draw.height())) { if (GPixel_GetA(*draw.getAddr(x, y)) > 0) { // blend float sA = c.fA + ((GPixel_GetA(*draw.getAddr(x, y)) / 255.99999) * (1.0f - c.fA)); float sR = (c.fR * c.fA) + ((GPixel_GetR(*draw.getAddr(x, y)) / 255.99999) * (1.0f - c.fA)); float sG = (c.fG * c.fA) + ((GPixel_GetG(*draw.getAddr(x, y)) / 255.99999) * (1.0f - c.fA)); float sB = (c.fB * c.fA) + ((GPixel_GetB(*draw.getAddr(x, y)) / 255.99999) * (1.0f - c.fA)); unsigned nA = (int)(sA * 255.99999); unsigned nR = (int)(sR * 255.99999); unsigned nG = (int)(sG * 255.99999); unsigned nB = (int)(sB * 255.99999); d[x] = GPixel_PackARGB(nA, nR, nG, nB); } else { d[x] = GPixel_PackARGB(a, r, g, b); } } if (eArr[currL].bottom == y) { currL = index; index++; } if (eArr[currR].bottom == y) { currR = index; index++; } } d = (GPixel*)((char*)d + draw.rowBytes()); } }
/** * Scale and translate the bitmap such that is fills the specific rectangle. * * Any area in the rectangle that is outside of the bounds of the canvas is ignored. * * If a given pixel in the bitmap is not opaque (e.g. GPixel_GetA() < 255) then blend it * using SRCOVER blend mode. */ void fillBitmapRect(const GBitmap& src, const GRect& dst) { // Store dimensions of dst rectangle int left = dst.left(); int top = dst.top(); int right = dst.right(); int bottom = dst.bottom(); int rW = dst.width(); int rH = dst.height(); // Store dimensions of src bitmap int bW = src.width(); int bH = src.height(); // Find necessary scale factor for both width and height float sx = (float)bW/(float)rW; float sy = (float)bH/(float)rH; float tx = 0 - left; float ty = 0 - top; // Create matrix for scaling float scale[6] = {sx, 0, 0, 0, sy, 0}; // Create matrix for translating float translate[6] = {1, 0, tx, 0, 1, ty}; // Combine the matrices float both[6] = {sx, 0, sx*tx, 0, sy, sy*ty}; // For loop that takes each point in dst and find corresponding point in src GPixel* d = draw.fPixels; d = (GPixel*)((char*)d + (int)top * draw.rowBytes()); for (int y = top; y < bottom; ++y) { for (int x = left; x < right; ++x) { // Only map pixels that are in the bitmap if ((x >= 0 && x < draw.width()) && (y >= 0 && y < draw.height())) { // Find corresponding point in src bitmap int xP; int yP; xP = both[0] * x + both[1] * y + both[2]; yP = both[3] * x + both[4] * y + both[5]; // Apply CTM to x and y int nX = (CTM[0] * x) + (CTM[1] * y) + (CTM[2]); int nY = (CTM[3] * x) + (CTM[4] * y) + (CTM[5]); // Prepare color to fill unsigned a = GPixel_GetA(*src.getAddr(xP, yP)); unsigned r = GPixel_GetR(*src.getAddr(xP, yP)); unsigned g = GPixel_GetG(*src.getAddr(xP, yP)); unsigned b = GPixel_GetB(*src.getAddr(xP, yP)); unsigned nA = a; unsigned nR = r; unsigned nG = g; unsigned nB = b; if (GPixel_GetA(*draw.getAddr(x, y)) > 0) { // blend float lA = GPixel_GetA(*src.getAddr(xP, yP)) / 255.99999; float lR = GPixel_GetR(*src.getAddr(xP, yP)) / 255.99999; float lG = GPixel_GetG(*src.getAddr(xP, yP)) / 255.99999; float lB = GPixel_GetB(*src.getAddr(xP, yP)) / 255.99999; float sA = lA + ((GPixel_GetA(*draw.getAddr(x, y)) / 255.99999) * (1.0f - lA)); float sR = lR + ((GPixel_GetR(*draw.getAddr(x, y)) / 255.99999) * (1.0f - lA)); float sG = lG + ((GPixel_GetG(*draw.getAddr(x, y)) / 255.99999) * (1.0f - lA)); float sB = lB + ((GPixel_GetB(*draw.getAddr(x, y)) / 255.99999) * (1.0f - lA)); /* float sA = lA + ((GPixel_GetA(*draw.getAddr(nX, nY)) / 255.99999) * (1.0f - lA)); float sR = lR + ((GPixel_GetR(*draw.getAddr(nX, nY)) / 255.99999) * (1.0f - lA)); float sG = lG + ((GPixel_GetG(*draw.getAddr(nX, nY)) / 255.99999) * (1.0f - lA)); float sB = lB + ((GPixel_GetB(*draw.getAddr(nX, nY)) / 255.99999) * (1.0f - lA)); */ nA = (int)(sA * 255.99999); nR = (int)(sR * 255.99999); nG = (int)(sG * 255.99999); nB = (int)(sB * 255.99999); d[x] = GPixel_PackARGB(nA, nR, nG, nB); //d = (GPixel*)((char*)d + (int)nY * draw.rowBytes()); //d[nX] = GPixel_PackARGB(nA, nR, nG, nB); //d = (GPixel*)((char*)d - (int)nY * draw.rowBytes()); } else { d[x] = GPixel_PackARGB(a, r, g, b); //d = (GPixel*)((char*)d + (int)nY * draw.rowBytes()); //d[nX] = GPixel_PackARGB(a, r, g, b); //d = (GPixel*)((char*)d - (int)nY * draw.rowBytes()); } } } d = (GPixel*)((char*)d + draw.rowBytes()); } }
// This code draws a bitmap assuming that we only have translation and scale, // which allows us to perform certain optimizations... void drawBitmapSimple(const GBitmap &bm, const GPaint &paint) { const GBitmap &ctxbm = GetInternalBitmap(); GRect ctxRect = GRect::MakeXYWH(0, 0, ctxbm.width(), ctxbm.height()); GRect bmRect = GRect::MakeXYWH(0, 0, bm.width(), bm.height()); GRect pixelRect = GetTransformedBoundingBox(bmRect); GRect rect; if(!(rect.setIntersection(ctxRect, pixelRect))) { return; } // We know that since we're only doing scale and translation, that all of the pixel // centers contained in rect are going to be drawn, so we only need to know the // dimensions of the mapping... GVec3f origin(0, 0, 1); GVec3f offset(1, 1, 1); origin = m_CTM * origin; offset = m_CTM * offset; float xScale = 1.0f / (offset.X() - origin.X()); float yScale = 1.0f / (offset.Y() - origin.Y()); GVec2f start = GVec2f(0, 0); if(xScale < 0.0f) { start.X() = pixelRect.fRight - 1.0f; } if(yScale < 0.0f) { start.Y() = pixelRect.fBottom - 1.0f; } GIRect dstRect = rect.round(); // Construct new bitmap int32_t offsetX = ::std::max(0, -dstRect.fLeft); int32_t offsetY = ::std::max(0, -dstRect.fTop); GBitmap fbm; fbm.fWidth = bm.width(); fbm.fHeight = bm.height(); fbm.fPixels = GetRow(bm, offsetY) + offsetX; fbm.fRowBytes = bm.fRowBytes; BlendFunc blend = blend_srcover; float alpha = paint.getAlpha(); if(alpha >= kOpaqueAlpha) { for(uint32_t j = 0; j < dstRect.height(); j++) { uint32_t srcIdxY = static_cast<uint32_t>(start.Y() + static_cast<float>(j) * yScale); GPixel *srcPixels = GetRow(fbm, Clamp<int>(srcIdxY, 0, fbm.height())); GPixel *dstPixels = GetRow(ctxbm, dstRect.fTop + j) + dstRect.fLeft; for(uint32_t i = 0; i < dstRect.width(); i++) { uint32_t srcIdxX = static_cast<uint32_t>(start.X() + static_cast<float>(i) * xScale); dstPixels[i] = blend(dstPixels[i], srcPixels[Clamp<int>(srcIdxX, 0, fbm.width())]); } } } else { const uint32_t alphaVal = static_cast<uint32_t>((alpha * 255.0f) + 0.5f); for(uint32_t j = 0; j < dstRect.height(); j++) { uint32_t srcIdxY = static_cast<uint32_t>(start.Y() + static_cast<float>(j) * yScale); GPixel *srcPixels = GetRow(fbm, srcIdxY); GPixel *dstPixels = GetRow(ctxbm, dstRect.fTop + j) + dstRect.fLeft; for(uint32_t i = 0; i < dstRect.width(); i++) { uint32_t srcIdxX = static_cast<uint32_t>(start.X() + static_cast<float>(i) * xScale); uint32_t srcA = fixed_multiply(GPixel_GetA(srcPixels[srcIdxX]), alphaVal); uint32_t srcR = fixed_multiply(GPixel_GetR(srcPixels[srcIdxX]), alphaVal); uint32_t srcG = fixed_multiply(GPixel_GetG(srcPixels[srcIdxX]), alphaVal); uint32_t srcB = fixed_multiply(GPixel_GetB(srcPixels[srcIdxX]), alphaVal); GPixel src = GPixel_PackARGB(srcA, srcR, srcG, srcB); dstPixels[i] = blend(dstPixels[i], src); } } } }