Skip to content
Merged
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
26 changes: 21 additions & 5 deletions image.go
Original file line number Diff line number Diff line change
Expand Up @@ -78,16 +78,23 @@ func (p *PixelPainter) DrawImage(dst Rect, src []byte, srcW, srcH int) {
continue
}

// A repeat is settled before anything is read from the source. prevSY
// only holds a source row that was written whole, so matching it proves
// the row is opaque as well as already built -- re-scanning its alphas
// would be half the bytes of the blit spent proving what is known.
if sy == prevSY && prevRow >= 0 {
copy(p.Buf[lo:hi], p.Buf[prevRow+eff.X*4:prevRow+(eff.X+eff.W)*4])
prevRow = dstRow
continue
}

// A fully opaque row replaces what was underneath, so it can be written
// without consulting it. A row with any translucency cannot: the result
// depends on the ground, which differs from row to row.
if rowOpaque(src[srcRow : srcRow+srcW*4]) {
switch {
case sy == prevSY && prevRow >= 0:
copy(p.Buf[lo:hi], p.Buf[prevRow+eff.X*4:prevRow+(eff.X+eff.W)*4])
case dst.W == srcW && eff.X == dst.X && eff.W == dst.W:
if dst.W == srcW && eff.X == dst.X && eff.W == dst.W {
copy(p.Buf[lo:hi], src[srcRow:srcRow+srcW*4])
default:
} else {
scaleRow(p.Buf[lo:hi], src[srcRow:srcRow+srcW*4], srcW, dst.W, eff.X-dst.X)
}
prevRow, prevSY = dstRow, sy
Expand Down Expand Up @@ -151,6 +158,15 @@ func scaleRow(dst, src []byte, srcW, dstW, x0 int) {

// rowOpaque reports whether every pixel of an RGBA row is fully opaque, which
// is what makes a wholesale copy equivalent to compositing it.
//
// The directive is not decoration. Inlined into the loop above -- which grew
// when the clip stopped being tested per pixel -- this scan measured 1,152,535
// ns/op on a full 1000x700 blit; kept out of line, the SAME code measures
// 238,252. The body is a tight strided scan whose cost lives entirely in
// register allocation, and folding it into an already register-hungry loop is
// what wrecks it. Remove the directive and the benchmark says so immediately.
//
//go:noinline
func rowOpaque(row []byte) bool {
for i := 3; i < len(row); i += 4 {
if row[i] != 0xFF {
Expand Down
Loading