Нашел на /r/proceduralgeneration пост в котором некто написал фильтр для изображений, который превращает исходное изображение в условно абстрактное. Код был написан на Lua, я же решил переписать предложенный метод на Go. Во-первых я никогда не писал что-то связанное с обработкой изображений на гошке, а во-вторых почему бы и нет.

package main

import (
	"image"
	"image/color"
	"image/jpeg"
	"os"
	"sort"
)

func main() {
	file, err := os.Open("in.jpg")
	if err != nil {
		panic(err)
	}
	defer file.Close()

	image.RegisterFormat("jpeg", "jpeg", jpeg.Decode, jpeg.DecodeConfig)

	img, _, err := image.Decode(file)
	if err != nil {
		panic(err)
	}

	rec := img.Bounds()
	cimg := image.NewRGBA(rec)

	m := make([]color.Color, rec.Dy())
	for x := 0; x < rec.Dx(); x++ {
		for y := 0; y < rec.Dy(); y++ {
			m[y] = img.At(x, y)
		}

		sort.SliceStable(m, func(i, j int) bool {
			return luma(m[i]) < luma(m[j])
		})

		for y := range m {
			cimg.Set(x, y, m[y])
		}
	}

	out, err := os.Create("out.jpg")
	if err != nil {
		panic(err)
	}

	err = jpeg.Encode(out, cimg, &jpeg.Options{Quality: 80})
	if err != nil {
		panic(err)
	}
}

func luma(c color.Color) float32 {
	r, g, b, _ := c.RGBA()

	return 0.2126*float32(r>>8) +
		0.7152*float32(g>>8) +
		0.0722*float32(b>>8)
}

TL;DR Что тут происходит? Берем пиксельную сетку изображения, делим на столбцы и в каждом из столбцов сортируем пиксели по относительной яркости.

Для вычисления относительной яркости нужно взять каждый пиксель, разложить на RGB цвета и умножить их на коэффициенты. Почему именно такие коэффициенты лучше почитать в статье на хабре. Результат выходит приличный, для изображений содержащих большой однородный задний фон.

Luma

Второй интересный момент момент это то, как в Go реализованы возвращаемые значения цветов. Все цвета хранятся в uint32 в диапазоне от 0 до 0xFFFF, что позволяет хранить эффективные значения для 16-битного диапазона. Как обычно, самый исчерпывающий ответ о том как это работает можно найти на SO, где же еще. Но так как формула относительной яркости работает с 8-битными значениями, то конвертируем 16-битное представление обратно в 8 бит используя битовый сдвиг вправо.