mirror of https://github.com/perkeep/perkeep.git
369 lines
11 KiB
Go
369 lines
11 KiB
Go
// Copyright 2011 The Go Authors. All rights reserved.
|
|
// Use of this source code is governed by a BSD-style
|
|
// license that can be found in the LICENSE file.
|
|
|
|
// Package resize resizes images.
|
|
package resize
|
|
|
|
import (
|
|
"image"
|
|
"image/color"
|
|
"image/draw"
|
|
)
|
|
|
|
// Resize returns a scaled copy of the image slice r of m.
|
|
// The returned image has width w and height h.
|
|
func Resize(m image.Image, r image.Rectangle, w, h int) image.Image {
|
|
if w < 0 || h < 0 {
|
|
return nil
|
|
}
|
|
if w == 0 || h == 0 || r.Dx() <= 0 || r.Dy() <= 0 {
|
|
return image.NewRGBA64(image.Rect(0, 0, w, h))
|
|
}
|
|
switch m := m.(type) {
|
|
case *image.RGBA:
|
|
return resizeRGBA(m, r, w, h)
|
|
case *image.YCbCr:
|
|
if m, ok := resizeYCbCr(m, r, w, h); ok {
|
|
return m
|
|
}
|
|
}
|
|
ww, hh := uint64(w), uint64(h)
|
|
dx, dy := uint64(r.Dx()), uint64(r.Dy())
|
|
// The scaling algorithm is to nearest-neighbor magnify the dx * dy source
|
|
// to a (ww*dx) * (hh*dy) intermediate image and then minify the intermediate
|
|
// image back down to a ww * hh destination with a simple box filter.
|
|
// The intermediate image is implied, we do not physically allocate a slice
|
|
// of length ww*dx*hh*dy.
|
|
// For example, consider a 4*3 source image. Label its pixels from a-l:
|
|
// abcd
|
|
// efgh
|
|
// ijkl
|
|
// To resize this to a 3*2 destination image, the intermediate is 12*6.
|
|
// Whitespace has been added to delineate the destination pixels:
|
|
// aaab bbcc cddd
|
|
// aaab bbcc cddd
|
|
// eeef ffgg ghhh
|
|
//
|
|
// eeef ffgg ghhh
|
|
// iiij jjkk klll
|
|
// iiij jjkk klll
|
|
// Thus, the 'b' source pixel contributes one third of its value to the
|
|
// (0, 0) destination pixel and two thirds to (1, 0).
|
|
// The implementation is a two-step process. First, the source pixels are
|
|
// iterated over and each source pixel's contribution to 1 or more
|
|
// destination pixels are summed. Second, the sums are divided by a scaling
|
|
// factor to yield the destination pixels.
|
|
// TODO: By interleaving the two steps, instead of doing all of
|
|
// step 1 first and all of step 2 second, we could allocate a smaller sum
|
|
// slice of length 4*w*2 instead of 4*w*h, although the resultant code
|
|
// would become more complicated.
|
|
n, sum := dx*dy, make([]uint64, 4*w*h)
|
|
for y := r.Min.Y; y < r.Max.Y; y++ {
|
|
for x := r.Min.X; x < r.Max.X; x++ {
|
|
// Get the source pixel.
|
|
r32, g32, b32, a32 := m.At(x, y).RGBA()
|
|
r64 := uint64(r32)
|
|
g64 := uint64(g32)
|
|
b64 := uint64(b32)
|
|
a64 := uint64(a32)
|
|
// Spread the source pixel over 1 or more destination rows.
|
|
py := uint64(y-r.Min.Y) * hh
|
|
for remy := hh; remy > 0; {
|
|
qy := dy - (py % dy)
|
|
if qy > remy {
|
|
qy = remy
|
|
}
|
|
// Spread the source pixel over 1 or more destination columns.
|
|
px := uint64(x-r.Min.X) * ww
|
|
index := 4 * ((py/dy)*ww + (px / dx))
|
|
for remx := ww; remx > 0; {
|
|
qx := dx - (px % dx)
|
|
if qx > remx {
|
|
qx = remx
|
|
}
|
|
sum[index+0] += r64 * qx * qy
|
|
sum[index+1] += g64 * qx * qy
|
|
sum[index+2] += b64 * qx * qy
|
|
sum[index+3] += a64 * qx * qy
|
|
index += 4
|
|
px += qx
|
|
remx -= qx
|
|
}
|
|
py += qy
|
|
remy -= qy
|
|
}
|
|
}
|
|
}
|
|
return average(sum, w, h, n*0x0101)
|
|
}
|
|
|
|
// average convert the sums to averages and returns the result.
|
|
func average(sum []uint64, w, h int, n uint64) image.Image {
|
|
ret := image.NewRGBA(image.Rect(0, 0, w, h))
|
|
for y := 0; y < h; y++ {
|
|
for x := 0; x < w; x++ {
|
|
i := y*ret.Stride + x*4
|
|
j := 4 * (y*w + x)
|
|
ret.Pix[i+0] = uint8(sum[j+0] / n)
|
|
ret.Pix[i+1] = uint8(sum[j+1] / n)
|
|
ret.Pix[i+2] = uint8(sum[j+2] / n)
|
|
ret.Pix[i+3] = uint8(sum[j+3] / n)
|
|
}
|
|
}
|
|
return ret
|
|
}
|
|
|
|
// resizeYCbCr returns a scaled copy of the YCbCr image slice r of m.
|
|
// The returned image has width w and height h.
|
|
func resizeYCbCr(m *image.YCbCr, r image.Rectangle, w, h int) (image.Image, bool) {
|
|
var verticalRes int
|
|
switch m.SubsampleRatio {
|
|
case image.YCbCrSubsampleRatio420:
|
|
verticalRes = 2
|
|
case image.YCbCrSubsampleRatio422:
|
|
verticalRes = 1
|
|
default:
|
|
return nil, false
|
|
}
|
|
ww, hh := uint64(w), uint64(h)
|
|
dx, dy := uint64(r.Dx()), uint64(r.Dy())
|
|
// See comment in Resize.
|
|
n, sum := dx*dy, make([]uint64, 4*w*h)
|
|
for y := r.Min.Y; y < r.Max.Y; y++ {
|
|
Y := m.Y[y*m.YStride:]
|
|
Cb := m.Cb[y/verticalRes*m.CStride:]
|
|
Cr := m.Cr[y/verticalRes*m.CStride:]
|
|
for x := r.Min.X; x < r.Max.X; x++ {
|
|
// Get the source pixel.
|
|
r8, g8, b8 := color.YCbCrToRGB(Y[x], Cb[x/2], Cr[x/2])
|
|
r64 := uint64(r8)
|
|
g64 := uint64(g8)
|
|
b64 := uint64(b8)
|
|
// Spread the source pixel over 1 or more destination rows.
|
|
py := uint64(y-r.Min.Y) * hh
|
|
for remy := hh; remy > 0; {
|
|
qy := dy - (py % dy)
|
|
if qy > remy {
|
|
qy = remy
|
|
}
|
|
// Spread the source pixel over 1 or more destination columns.
|
|
px := uint64(x-r.Min.X) * ww
|
|
index := 4 * ((py/dy)*ww + (px / dx))
|
|
for remx := ww; remx > 0; {
|
|
qx := dx - (px % dx)
|
|
if qx > remx {
|
|
qx = remx
|
|
}
|
|
qxy := qx * qy
|
|
sum[index+0] += r64 * qxy
|
|
sum[index+1] += g64 * qxy
|
|
sum[index+2] += b64 * qxy
|
|
sum[index+3] += 0xFFFF * qxy
|
|
index += 4
|
|
px += qx
|
|
remx -= qx
|
|
}
|
|
py += qy
|
|
remy -= qy
|
|
}
|
|
}
|
|
}
|
|
return average(sum, w, h, n), true
|
|
}
|
|
|
|
// resizeRGBA returns a scaled copy of the RGBA image slice r of m.
|
|
// The returned image has width w and height h.
|
|
func resizeRGBA(m *image.RGBA, r image.Rectangle, w, h int) image.Image {
|
|
ww, hh := uint64(w), uint64(h)
|
|
dx, dy := uint64(r.Dx()), uint64(r.Dy())
|
|
// See comment in Resize.
|
|
n, sum := dx*dy, make([]uint64, 4*w*h)
|
|
for y := r.Min.Y; y < r.Max.Y; y++ {
|
|
pix := m.Pix[(y-m.Rect.Min.Y)*m.Stride:]
|
|
for x := r.Min.X; x < r.Max.X; x++ {
|
|
// Get the source pixel.
|
|
p := pix[(x-m.Rect.Min.X)*4:]
|
|
r64 := uint64(p[0])
|
|
g64 := uint64(p[1])
|
|
b64 := uint64(p[2])
|
|
a64 := uint64(p[3])
|
|
// Spread the source pixel over 1 or more destination rows.
|
|
py := uint64(y-r.Min.Y) * hh
|
|
for remy := hh; remy > 0; {
|
|
qy := dy - (py % dy)
|
|
if qy > remy {
|
|
qy = remy
|
|
}
|
|
// Spread the source pixel over 1 or more destination columns.
|
|
px := uint64(x-r.Min.X) * ww
|
|
index := 4 * ((py/dy)*ww + (px / dx))
|
|
for remx := ww; remx > 0; {
|
|
qx := dx - (px % dx)
|
|
if qx > remx {
|
|
qx = remx
|
|
}
|
|
qxy := qx * qy
|
|
sum[index+0] += r64 * qxy
|
|
sum[index+1] += g64 * qxy
|
|
sum[index+2] += b64 * qxy
|
|
sum[index+3] += a64 * qxy
|
|
index += 4
|
|
px += qx
|
|
remx -= qx
|
|
}
|
|
py += qy
|
|
remy -= qy
|
|
}
|
|
}
|
|
}
|
|
return average(sum, w, h, n)
|
|
}
|
|
|
|
// HalveInplace downsamples the image by 50% using averaging interpolation.
|
|
func HalveInplace(m image.Image) image.Image {
|
|
b := m.Bounds()
|
|
switch m := m.(type) {
|
|
case *image.YCbCr:
|
|
for y := b.Min.Y; y < b.Max.Y/2; y++ {
|
|
for x := b.Min.X; x < b.Max.X/2; x++ {
|
|
y00 := uint32(m.Y[m.YOffset(2*x, 2*y)])
|
|
y10 := uint32(m.Y[m.YOffset(2*x+1, 2*y)])
|
|
y01 := uint32(m.Y[m.YOffset(2*x, 2*y+1)])
|
|
y11 := uint32(m.Y[m.YOffset(2*x+1, 2*y+1)])
|
|
// Add before divide with uint32 or we get errors in the least
|
|
// significant bits.
|
|
m.Y[m.YOffset(x, y)] = uint8((y00 + y10 + y01 + y11) >> 2)
|
|
|
|
cb00 := uint32(m.Cb[m.COffset(2*x, 2*y)])
|
|
cb10 := uint32(m.Cb[m.COffset(2*x+1, 2*y)])
|
|
cb01 := uint32(m.Cb[m.COffset(2*x, 2*y+1)])
|
|
cb11 := uint32(m.Cb[m.COffset(2*x+1, 2*y+1)])
|
|
m.Cb[m.COffset(x, y)] = uint8((cb00 + cb10 + cb01 + cb11) >> 2)
|
|
|
|
cr00 := uint32(m.Cr[m.COffset(2*x, 2*y)])
|
|
cr10 := uint32(m.Cr[m.COffset(2*x+1, 2*y)])
|
|
cr01 := uint32(m.Cr[m.COffset(2*x, 2*y+1)])
|
|
cr11 := uint32(m.Cr[m.COffset(2*x+1, 2*y+1)])
|
|
m.Cr[m.COffset(x, y)] = uint8((cr00 + cr10 + cr01 + cr11) >> 2)
|
|
}
|
|
}
|
|
b.Max = b.Min.Add(b.Size().Div(2))
|
|
return subImage(m, b)
|
|
case draw.Image:
|
|
for y := b.Min.Y; y < b.Max.Y/2; y++ {
|
|
for x := b.Min.X; x < b.Max.X/2; x++ {
|
|
r00, g00, b00, a00 := m.At(2*x, 2*y).RGBA()
|
|
r10, g10, b10, a10 := m.At(2*x+1, 2*y).RGBA()
|
|
r01, g01, b01, a01 := m.At(2*x, 2*y+1).RGBA()
|
|
r11, g11, b11, a11 := m.At(2*x+1, 2*y+1).RGBA()
|
|
|
|
// Add before divide with uint32 or we get errors in the least
|
|
// significant bits.
|
|
r := (r00 + r10 + r01 + r11) >> 2
|
|
g := (g00 + g10 + g01 + g11) >> 2
|
|
b := (b00 + b10 + b01 + b11) >> 2
|
|
a := (a00 + a10 + a01 + a11) >> 2
|
|
|
|
m.Set(x, y, color.RGBA{
|
|
R: uint8(r >> 8),
|
|
G: uint8(g >> 8),
|
|
B: uint8(b >> 8),
|
|
A: uint8(a >> 8),
|
|
})
|
|
}
|
|
}
|
|
b.Max = b.Min.Add(b.Size().Div(2))
|
|
return subImage(m, b)
|
|
default:
|
|
// TODO(wathiede): fallback to generic Resample somehow?
|
|
panic("Unhandled image type")
|
|
}
|
|
}
|
|
|
|
// ResampleInplace will resample m inplace, overwritting existing pixel data,
|
|
// and return a subimage of m sized to w and h.
|
|
func ResampleInplace(m image.Image, r image.Rectangle, w, h int) image.Image {
|
|
// We don't support scaling up.
|
|
if r.Dx() < w || r.Dy() < h {
|
|
return m
|
|
}
|
|
|
|
switch m := m.(type) {
|
|
case *image.YCbCr:
|
|
xStep := float64(r.Dx()) / float64(w)
|
|
yStep := float64(r.Dy()) / float64(h)
|
|
for y := r.Min.Y; y < r.Min.Y+h; y++ {
|
|
for x := r.Min.X; x < r.Min.X+w; x++ {
|
|
xSrc := int(float64(x) * xStep)
|
|
ySrc := int(float64(y) * yStep)
|
|
cSrc := m.COffset(xSrc, ySrc)
|
|
cDst := m.COffset(x, y)
|
|
m.Y[m.YOffset(x, y)] = m.Y[m.YOffset(xSrc, ySrc)]
|
|
m.Cb[cDst] = m.Cb[cSrc]
|
|
m.Cr[cDst] = m.Cr[cSrc]
|
|
}
|
|
}
|
|
case draw.Image:
|
|
xStep := float64(r.Dx()) / float64(w)
|
|
yStep := float64(r.Dy()) / float64(h)
|
|
for y := r.Min.Y; y < r.Min.Y+h; y++ {
|
|
for x := r.Min.X; x < r.Min.X+w; x++ {
|
|
xSrc := int(float64(x) * xStep)
|
|
ySrc := int(float64(y) * yStep)
|
|
r, g, b, a := m.At(xSrc, ySrc).RGBA()
|
|
m.Set(x, y, color.RGBA{
|
|
R: uint8(r >> 8),
|
|
G: uint8(g >> 8),
|
|
B: uint8(b >> 8),
|
|
A: uint8(a >> 8),
|
|
})
|
|
}
|
|
}
|
|
default:
|
|
// TODO fallback to generic Resample somehow?
|
|
panic("Unhandled image type")
|
|
}
|
|
r.Max.X = r.Min.X + w
|
|
r.Max.Y = r.Min.Y + h
|
|
return subImage(m, r)
|
|
}
|
|
|
|
func subImage(m image.Image, r image.Rectangle) image.Image {
|
|
type subImager interface {
|
|
SubImage(image.Rectangle) image.Image
|
|
}
|
|
if si, ok := m.(subImager); ok {
|
|
return si.SubImage(r)
|
|
}
|
|
panic("Image type doesn't support SubImage")
|
|
}
|
|
|
|
// Resample returns a resampled copy of the image slice r of m.
|
|
// The returned image has width w and height h.
|
|
func Resample(m image.Image, r image.Rectangle, w, h int) image.Image {
|
|
if w < 0 || h < 0 {
|
|
return nil
|
|
}
|
|
if w == 0 || h == 0 || r.Dx() <= 0 || r.Dy() <= 0 {
|
|
return image.NewRGBA64(image.Rect(0, 0, w, h))
|
|
}
|
|
img := image.NewRGBA(image.Rect(0, 0, w, h))
|
|
xStep := float64(r.Dx()) / float64(w)
|
|
yStep := float64(r.Dy()) / float64(h)
|
|
for y := 0; y < h; y++ {
|
|
for x := 0; x < w; x++ {
|
|
xSrc := int(float64(r.Min.X) + float64(x)*xStep)
|
|
ySrc := int(float64(r.Min.Y) + float64(y)*yStep)
|
|
r, g, b, a := m.At(xSrc, ySrc).RGBA()
|
|
img.SetRGBA(x, y, color.RGBA{
|
|
R: uint8(r >> 8),
|
|
G: uint8(g >> 8),
|
|
B: uint8(b >> 8),
|
|
A: uint8(a >> 8),
|
|
})
|
|
}
|
|
}
|
|
return img
|
|
}
|