honk/image.go

149 lines
3.9 KiB
Go
Raw Normal View History

2019-04-09 13:59:33 +02:00
//
// Copyright (c) 2019 Ted Unangst <tedu@tedunangst.com>
//
// Permission to use, copy, modify, and distribute this software for any
// purpose with or without fee is hereby granted, provided that the above
// copyright notice and this permission notice appear in all copies.
//
// THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
// WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
// MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
// ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
// WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
// ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
// OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
package main
import (
"bytes"
"fmt"
"image"
"image/jpeg"
"image/png"
"math"
)
func lineate(s uint8) float64 {
x := float64(s)
x /= 255.0
if x < 0.04045 {
x /= 12.92
} else {
x += 0.055
x /= 1.055
x = math.Pow(x, 2.4)
}
return x
}
func delineate(x float64) uint8 {
if x > 0.0031308 {
x = math.Pow(x, 1/2.4)
x *= 1.055
x -= 0.055
} else {
x *= 12.92
}
x *= 255.0
return uint8(x)
}
func blend(d []byte, s1, s2, s3, s4 int) byte {
l1 := lineate(d[s1])
l2 := lineate(d[s2])
l3 := lineate(d[s3])
l4 := lineate(d[s4])
return delineate((l1 + l2 + l3 + l4) / 4.0)
}
func squish(d []byte, s1, s2, s3, s4 int) byte {
return uint8((uint32(d[s1]) + uint32(d[s2]) + uint32(d[s3]) + uint32(d[s4])) / 4)
2019-04-09 13:59:33 +02:00
}
func vacuumwrap(img image.Image, format string) ([]byte, string, error) {
maxdimension := 2048
for img.Bounds().Max.X > maxdimension || img.Bounds().Max.Y > maxdimension {
switch oldimg := img.(type) {
case *image.NRGBA:
w, h := oldimg.Rect.Max.X/2, oldimg.Rect.Max.Y/2
newimg := image.NewNRGBA(image.Rectangle{Max: image.Point{X: w, Y: h}})
for j := 0; j < h; j++ {
for i := 0; i < w; i++ {
p := newimg.Stride*j + i*4
q1 := oldimg.Stride*(j*2+0) + i*4*2
q2 := oldimg.Stride*(j*2+1) + i*4*2
newimg.Pix[p+0] = blend(oldimg.Pix, q1+0, q1+4, q2+0, q2+4)
newimg.Pix[p+1] = blend(oldimg.Pix, q1+1, q1+5, q2+1, q2+5)
newimg.Pix[p+2] = blend(oldimg.Pix, q1+2, q1+6, q2+2, q2+6)
newimg.Pix[p+3] = squish(oldimg.Pix, q1+3, q1+7, q2+3, q2+7)
}
}
img = newimg
case *image.YCbCr:
w, h := oldimg.Rect.Max.X/2, oldimg.Rect.Max.Y/2
newimg := image.NewYCbCr(image.Rectangle{Max: image.Point{X: w, Y: h}},
oldimg.SubsampleRatio)
for j := 0; j < h; j++ {
for i := 0; i < w; i++ {
p := newimg.YStride*j + i
q1 := oldimg.YStride*(j*2+0) + i*2
q2 := oldimg.YStride*(j*2+1) + i*2
newimg.Y[p+0] = blend(oldimg.Y, q1+0, q1+1, q2+0, q2+1)
}
}
switch newimg.SubsampleRatio {
case image.YCbCrSubsampleRatio444:
w, h = w, h
case image.YCbCrSubsampleRatio422:
w, h = w/2, h
case image.YCbCrSubsampleRatio420:
w, h = w/2, h/2
case image.YCbCrSubsampleRatio440:
w, h = w, h/2
case image.YCbCrSubsampleRatio411:
w, h = w/4, h
case image.YCbCrSubsampleRatio410:
w, h = w/4, h/2
}
for j := 0; j < h; j++ {
for i := 0; i < w; i++ {
p := newimg.CStride*j + i
q1 := oldimg.CStride*(j*2+0) + i*2
q2 := oldimg.CStride*(j*2+1) + i*2
newimg.Cb[p+0] = blend(oldimg.Cb, q1+0, q1+1, q2+0, q2+1)
newimg.Cr[p+0] = blend(oldimg.Cr, q1+0, q1+1, q2+0, q2+1)
}
}
img = newimg
default:
return nil, "", fmt.Errorf("can't support image format")
}
}
maxsize := 512 * 1024
quality := 80
var buf bytes.Buffer
for {
switch format {
case "png":
png.Encode(&buf, img)
case "jpeg":
jpeg.Encode(&buf, img, &jpeg.Options{Quality: quality})
default:
return nil, "", fmt.Errorf("can't encode format: %s", format)
}
if buf.Len() > maxsize && quality > 30 {
switch format {
case "png":
format = "jpeg"
case "jpeg":
quality -= 10
}
buf.Reset()
continue
}
break
}
return buf.Bytes(), format, nil
}