all repos — honk @ 5919024da1616a2634b0a2bb121d21076f604350

my fork of honk

image.go (view raw)

  1//
  2// Copyright (c) 2019 Ted Unangst <tedu@tedunangst.com>
  3//
  4// Permission to use, copy, modify, and distribute this software for any
  5// purpose with or without fee is hereby granted, provided that the above
  6// copyright notice and this permission notice appear in all copies.
  7//
  8// THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
  9// WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
 10// MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
 11// ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
 12// WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
 13// ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
 14// OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
 15
 16package main
 17
 18import (
 19	"bytes"
 20	"fmt"
 21	"image"
 22	"image/jpeg"
 23	"image/png"
 24	"math"
 25)
 26
 27func lineate(s uint8) float64 {
 28	x := float64(s)
 29	x /= 255.0
 30	if x < 0.04045 {
 31		x /= 12.92
 32	} else {
 33		x += 0.055
 34		x /= 1.055
 35		x = math.Pow(x, 2.4)
 36	}
 37	return x
 38}
 39
 40func delineate(x float64) uint8 {
 41	if x > 0.0031308 {
 42		x = math.Pow(x, 1/2.4)
 43		x *= 1.055
 44		x -= 0.055
 45	} else {
 46		x *= 12.92
 47	}
 48	x *= 255.0
 49	return uint8(x)
 50}
 51
 52func blend(d []byte, s1, s2, s3, s4 int) byte {
 53	l1 := lineate(d[s1])
 54	l2 := lineate(d[s2])
 55	l3 := lineate(d[s3])
 56	l4 := lineate(d[s4])
 57	return delineate((l1 + l2 + l3 + l4) / 4.0)
 58}
 59
 60func squish(d []byte, s1, s2, s3, s4 int) byte {
 61	return uint8((uint32(s1) + uint32(s2)) / 2)
 62}
 63
 64func vacuumwrap(img image.Image, format string) ([]byte, string, error) {
 65	maxdimension := 2048
 66	for img.Bounds().Max.X > maxdimension || img.Bounds().Max.Y > maxdimension {
 67		switch oldimg := img.(type) {
 68		case *image.NRGBA:
 69			w, h := oldimg.Rect.Max.X/2, oldimg.Rect.Max.Y/2
 70			newimg := image.NewNRGBA(image.Rectangle{Max: image.Point{X: w, Y: h}})
 71			for j := 0; j < h; j++ {
 72				for i := 0; i < w; i++ {
 73					p := newimg.Stride*j + i*4
 74					q1 := oldimg.Stride*(j*2+0) + i*4*2
 75					q2 := oldimg.Stride*(j*2+1) + i*4*2
 76					newimg.Pix[p+0] = blend(oldimg.Pix, q1+0, q1+4, q2+0, q2+4)
 77					newimg.Pix[p+1] = blend(oldimg.Pix, q1+1, q1+5, q2+1, q2+5)
 78					newimg.Pix[p+2] = blend(oldimg.Pix, q1+2, q1+6, q2+2, q2+6)
 79					newimg.Pix[p+3] = squish(oldimg.Pix, q1+3, q1+7, q2+3, q2+7)
 80				}
 81			}
 82			img = newimg
 83		case *image.YCbCr:
 84			w, h := oldimg.Rect.Max.X/2, oldimg.Rect.Max.Y/2
 85			newimg := image.NewYCbCr(image.Rectangle{Max: image.Point{X: w, Y: h}},
 86				oldimg.SubsampleRatio)
 87			for j := 0; j < h; j++ {
 88				for i := 0; i < w; i++ {
 89					p := newimg.YStride*j + i
 90					q1 := oldimg.YStride*(j*2+0) + i*2
 91					q2 := oldimg.YStride*(j*2+1) + i*2
 92					newimg.Y[p+0] = blend(oldimg.Y, q1+0, q1+1, q2+0, q2+1)
 93				}
 94			}
 95			switch newimg.SubsampleRatio {
 96			case image.YCbCrSubsampleRatio444:
 97				w, h = w, h
 98			case image.YCbCrSubsampleRatio422:
 99				w, h = w/2, h
100			case image.YCbCrSubsampleRatio420:
101				w, h = w/2, h/2
102			case image.YCbCrSubsampleRatio440:
103				w, h = w, h/2
104			case image.YCbCrSubsampleRatio411:
105				w, h = w/4, h
106			case image.YCbCrSubsampleRatio410:
107				w, h = w/4, h/2
108			}
109			for j := 0; j < h; j++ {
110				for i := 0; i < w; i++ {
111					p := newimg.CStride*j + i
112					q1 := oldimg.CStride*(j*2+0) + i*2
113					q2 := oldimg.CStride*(j*2+1) + i*2
114					newimg.Cb[p+0] = blend(oldimg.Cb, q1+0, q1+1, q2+0, q2+1)
115					newimg.Cr[p+0] = blend(oldimg.Cr, q1+0, q1+1, q2+0, q2+1)
116				}
117			}
118			img = newimg
119		default:
120			return nil, "", fmt.Errorf("can't support image format")
121		}
122	}
123	maxsize := 512 * 1024
124	quality := 80
125	var buf bytes.Buffer
126	for {
127		switch format {
128		case "png":
129			png.Encode(&buf, img)
130		case "jpeg":
131			jpeg.Encode(&buf, img, &jpeg.Options{Quality: quality})
132		default:
133			return nil, "", fmt.Errorf("can't encode format: %s", format)
134		}
135		if buf.Len() > maxsize && quality > 30 {
136			switch format {
137			case "png":
138				format = "jpeg"
139			case "jpeg":
140				quality -= 10
141			}
142			buf.Reset()
143			continue
144		}
145		break
146	}
147	return buf.Bytes(), format, nil
148}