// DrawMask将dst上的r.Min,src上的sp,mask上的mp对齐,然后对dst上的r型矩阵区域执行Porter-Duff合并操作。 // mask设置为nil就代表完全不透明。 func DrawMask(dst Image, r image.Rectangle, src image.Image, sp image.Point, mask image.Image, mp image.Point, op Op) { clip(dst, &r, src, &sp, mask, &mp) if r.Empty() { return } // Fast paths for special cases. If none of them apply, then we fall back to a general but slow implementation. switch dst0 := dst.(type) { case *image.RGBA: if op == Over { if mask == nil { switch src0 := src.(type) { case *image.Uniform: drawFillOver(dst0, r, src0) return case *image.RGBA: drawCopyOver(dst0, r, src0, sp) return case *image.NRGBA: drawNRGBAOver(dst0, r, src0, sp) return case *image.YCbCr: // An image.YCbCr is always fully opaque, and so if the // mask is nil (i.e. fully opaque) then the op is // effectively always Src. Similarly for image.Gray and // image.CMYK. if imageutil.DrawYCbCr(dst0, r, src0, sp) { return } case *image.Gray: drawGray(dst0, r, src0, sp) return case *image.CMYK: drawCMYK(dst0, r, src0, sp) return } } else if mask0, ok := mask.(*image.Alpha); ok { switch src0 := src.(type) { case *image.Uniform: drawGlyphOver(dst0, r, src0, mask0, mp) return } } } else { if mask == nil { switch src0 := src.(type) { case *image.Uniform: drawFillSrc(dst0, r, src0) return case *image.RGBA: drawCopySrc(dst0, r, src0, sp) return case *image.NRGBA: drawNRGBASrc(dst0, r, src0, sp) return case *image.YCbCr: if imageutil.DrawYCbCr(dst0, r, src0, sp) { return } case *image.Gray: drawGray(dst0, r, src0, sp) return case *image.CMYK: drawCMYK(dst0, r, src0, sp) return } } } drawRGBA(dst0, r, src, sp, mask, mp, op) return case *image.Paletted: if op == Src && mask == nil && !processBackward(dst, r, src, sp) { drawPaletted(dst0, r, src, sp, false) return } } x0, x1, dx := r.Min.X, r.Max.X, 1 y0, y1, dy := r.Min.Y, r.Max.Y, 1 if processBackward(dst, r, src, sp) { x0, x1, dx = x1-1, x0-1, -1 y0, y1, dy = y1-1, y0-1, -1 } var out color.RGBA64 sy := sp.Y + y0 - r.Min.Y my := mp.Y + y0 - r.Min.Y for y := y0; y != y1; y, sy, my = y+dy, sy+dy, my+dy { sx := sp.X + x0 - r.Min.X mx := mp.X + x0 - r.Min.X for x := x0; x != x1; x, sx, mx = x+dx, sx+dx, mx+dx { ma := uint32(m) if mask != nil { _, _, _, ma = mask.At(mx, my).RGBA() } switch { case ma == 0: if op == Over { // No-op. } else { dst.Set(x, y, color.Transparent) } case ma == m && op == Src: dst.Set(x, y, src.At(sx, sy)) default: sr, sg, sb, sa := src.At(sx, sy).RGBA() if op == Over { dr, dg, db, da := dst.At(x, y).RGBA() a := m - (sa * ma / m) out.R = uint16((dr*a + sr*ma) / m) out.G = uint16((dg*a + sg*ma) / m) out.B = uint16((db*a + sb*ma) / m) out.A = uint16((da*a + sa*ma) / m) } else { out.R = uint16(sr * ma / m) out.G = uint16(sg * ma / m) out.B = uint16(sb * ma / m) out.A = uint16(sa * ma / m) } // The third argument is &out instead of out (and out is // declared outside of the inner loop) to avoid the implicit // conversion to color.Color here allocating memory in the // inner loop if sizeof(color.RGBA64) > sizeof(uintptr). dst.Set(x, y, &out) } } } }
// applyBlack combines d.img3 and d.blackPix into a CMYK image. The formula // used depends on whether the JPEG image is stored as CMYK or YCbCrK, // indicated by the APP14 (Adobe) metadata. // // Adobe CMYK JPEG images are inverted, where 255 means no ink instead of full // ink, so we apply "v = 255 - v" at various points. Note that a double // inversion is a no-op, so inversions might be implicit in the code below. func (d *decoder) applyBlack() (image.Image, error) { if !d.adobeTransformValid { return nil, UnsupportedError("unknown color model: 4-component JPEG doesn't have Adobe APP14 metadata") } // If the 4-component JPEG image isn't explicitly marked as "Unknown (RGB // or CMYK)" as per // http://www.sno.phy.queensu.ca/~phil/exiftool/TagNames/JPEG.html#Adobe // we assume that it is YCbCrK. This matches libjpeg's jdapimin.c. if d.adobeTransform != adobeTransformUnknown { // Convert the YCbCr part of the YCbCrK to RGB, invert the RGB to get // CMY, and patch in the original K. The RGB to CMY inversion cancels // out the 'Adobe inversion' described in the applyBlack doc comment // above, so in practice, only the fourth channel (black) is inverted. bounds := d.img3.Bounds() img := image.NewRGBA(bounds) imageutil.DrawYCbCr(img, bounds, d.img3, bounds.Min) for iBase, y := 0, bounds.Min.Y; y < bounds.Max.Y; iBase, y = iBase+img.Stride, y+1 { for i, x := iBase+3, bounds.Min.X; x < bounds.Max.X; i, x = i+4, x+1 { img.Pix[i] = 255 - d.blackPix[(y-bounds.Min.Y)*d.blackStride+(x-bounds.Min.X)] } } return &image.CMYK{ Pix: img.Pix, Stride: img.Stride, Rect: img.Rect, }, nil } // The first three channels (cyan, magenta, yellow) of the CMYK // were decoded into d.img3, but each channel was decoded into a separate // []byte slice, and some channels may be subsampled. We interleave the // separate channels into an image.CMYK's single []byte slice containing 4 // contiguous bytes per pixel. bounds := d.img3.Bounds() img := image.NewCMYK(bounds) translations := [4]struct { src []byte stride int }{ {d.img3.Y, d.img3.YStride}, {d.img3.Cb, d.img3.CStride}, {d.img3.Cr, d.img3.CStride}, {d.blackPix, d.blackStride}, } for t, translation := range translations { subsample := d.comp[t].h != d.comp[0].h || d.comp[t].v != d.comp[0].v for iBase, y := 0, bounds.Min.Y; y < bounds.Max.Y; iBase, y = iBase+img.Stride, y+1 { sy := y - bounds.Min.Y if subsample { sy /= 2 } for i, x := iBase+t, bounds.Min.X; x < bounds.Max.X; i, x = i+4, x+1 { sx := x - bounds.Min.X if subsample { sx /= 2 } img.Pix[i] = 255 - translation.src[sy*translation.stride+sx] } } } return img, nil }