Old-school 1bit error diffusion dithering algorithms. https://oppenlab.net/
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
 

724 lines
23 KiB

package online.fisk.filters
import kotlin.concurrent.thread
abstract class FilterImage {
abstract var width: Int
abstract var height: Int
abstract fun getPixel(x: Int, y: Int): Int
abstract fun setPixel(x: Int, y: Int, colour: Int)
}
abstract class Filter {
enum class AvailableFilters(val filterName: String, val filter: Filter){
Atkinson("Atkinson", FilterAtkinson()),
BayerTwoByTwo("2x2Bayer", Filter2By2Bayer()),
BayerThreeByThree("3x3Bayer", Filter3By3Bayer()),
BayerFourByFour("4x4Bayer", Filter4By4Bayer()),
BayerEightByEight("8x8Bayer", Filter8By48ayer()),
BayerFiveByThree("5x3Bayer", Filter5By3Bayer()),
Burkes("Burkes", FilterBurkes()),
FalseFloydSteinberg("FalseFloydSteinberg", FilterFalseFloydSteinberg()),
FloydSteinberg("FloydSteinberg", FilterFloydSteinberg()),
JarvisJudiceNinke("JarvisJudiceNinke", FilterJarvisJudiceNinke()),
LeftToRightErrorDiffusion("LeftToRightErrorDiffusion", FilterLeftToRightErrorDiffusion()),
NewspaperHalftone("NewspaperHalftone", FilterNewspaperHalftone()),
Random("Random", FilterRandom()),
Sierra("Sierra", FilterSierra()),
SierraLite("SierraLite", FilterSierraLite()),
Stucki("Stucki", FilterStucki()),
Threshold("Threshold", FilterThreshold()),
TwoRowSierra("TwoRowSierra", FilterTwoRowSierra())
}
companion object {
var threshold = 128
const val BLACK = 0x000000
const val WHITE = 0xffffff
}
abstract fun process(source: FilterImage, destination: FilterImage, callback: () -> Unit)
fun threshold(value: Int): Filter {
threshold = value
return this
}
}
class FilterError: Filter(){
override fun process(source: FilterImage, destination: FilterImage, callback: () -> Unit) {
System.out.println("Filter name not recognised")
callback()
}
}
class Filter2By2Bayer : Filter() {
override fun process(source: FilterImage, destination: FilterImage, callback: () -> Unit) {
thread {
val width = source.width
val height = source.height
val matrix = arrayOf(
intArrayOf(1, 3),
intArrayOf(4, 2)
)
for (y in 0 until height) {
for (x in 0 until width) {
var gray = source.getPixel(x, y) shr 16 and 0xFF
gray += gray * matrix[x % 2][y % 2] / 5
when {
gray < threshold -> destination.setPixel(x, y, BLACK)
else -> destination.setPixel(x, y, WHITE)
}
}
}
callback()
}
}
}
class Filter3By3Bayer : Filter() {
override fun process(source: FilterImage, destination: FilterImage, callback: () -> Unit) {
thread {
val width = source.width
val height = source.height
val matrix = arrayOf(
intArrayOf(3, 7, 4),
intArrayOf(6, 1, 9),
intArrayOf(2, 8, 5)
)
for (y in 0 until height) {
for (x in 0 until width) {
var gray = source.getPixel(x, y) shr 16 and 0xFF
gray += gray * matrix[x % 3][y % 3] / 10
when {
gray < threshold -> destination.setPixel(x, y, BLACK)
else -> destination.setPixel(x, y, WHITE)
}
}
}
callback()
}
}
}
class Filter4By4Bayer : Filter() {
override fun process(source: FilterImage, destination: FilterImage, callback: () -> Unit) {
thread {
val width = source.width
val height = source.height
val matrix = arrayOf(
intArrayOf(1, 9, 3, 11),
intArrayOf(13, 5, 15, 7),
intArrayOf(4, 12, 2, 10),
intArrayOf(16, 8, 14, 6)
)
for (y in 0 until height) {
for (x in 0 until width) {
var gray = source.getPixel(x, y) shr 16 and 0xFF
gray += gray * matrix[x % 4][y % 4] / 17
when {
gray < threshold -> destination.setPixel(x, y, BLACK)
else -> destination.setPixel(x, y, WHITE)
}
}
}
callback()
}
}
}
class Filter8By48ayer : Filter() {
override fun process(source: FilterImage, destination: FilterImage, callback: () -> Unit) {
thread {
val width = source.width
val height = source.height
val matrix = arrayOf(
intArrayOf(1, 49, 13, 61, 4, 52, 16, 64),
intArrayOf(33, 17, 45, 29, 36, 20, 48, 32),
intArrayOf(9, 57, 5, 53, 12, 60, 8, 56),
intArrayOf(41, 25, 37, 21, 44, 28, 40, 24),
intArrayOf(3, 51, 15, 63, 2, 50, 14, 62),
intArrayOf(35, 19, 47, 31, 34, 18, 46, 30),
intArrayOf(11, 59, 7, 55, 10, 58, 6, 54),
intArrayOf(43, 27, 39, 23, 42, 26, 38, 22)
)
for (y in 0 until height) {
for (x in 0 until width) {
var gray = source.getPixel(x, y) shr 16 and 0xFF
gray += gray * matrix[x % 8][y % 8] / 65
when {
gray < threshold -> destination.setPixel(x, y, BLACK)
else -> destination.setPixel(x, y, WHITE)
}
}
}
callback()
}
}
}
class Filter5By3Bayer : Filter() {
override fun process(source: FilterImage, destination: FilterImage, callback: () -> Unit) {
thread {
val width = source.width
val height = source.height
val matrix = arrayOf(
intArrayOf(9, 3, 0, 6, 12),
intArrayOf(10, 4, 1, 7, 13),
intArrayOf(11, 5, 2, 8, 14)
)
for (y in 0 until height) {
for (x in 0 until width) {
var gray = source.getPixel(x, y) shr 16 and 0xFF
//Horizontal:
gray += gray * matrix[x % 3][y % 5] / 16
//Vertical:
//gray += gray * matrix[y % 3][x % 5] / 16
when {
gray < threshold -> destination.setPixel(x, y, BLACK)
else -> destination.setPixel(x, y, WHITE)
}
}
}
callback()
}
}
}
class FilterNewspaperHalftone: Filter(){
override fun process(source: FilterImage, destination: FilterImage, callback: () -> Unit) {
thread {
val width = source.width
val height = source.height
val matrix = arrayOf(
intArrayOf(24, 10, 12, 26, 35, 47, 49, 37),
intArrayOf(8, 0, 2, 14, 45, 59, 61, 51),
intArrayOf(22, 6, 4, 16, 43, 57, 63, 53),
intArrayOf(30, 20, 18, 28, 33, 41, 55, 39),
intArrayOf(34, 46, 48, 36, 25, 11, 13, 27),
intArrayOf(44, 58, 60, 50, 9, 1, 3, 15),
intArrayOf(42, 56, 62, 52, 23, 7, 5, 17),
intArrayOf(32, 40, 54, 38, 31, 21, 19, 29)
)
for (y in 0 until height) {
for (x in 0 until width) {
var gray = source.getPixel(x, y) shr 16 and 0xFF
gray += gray * matrix[x % 8][y % 8] / 65
when {
gray < threshold -> destination.setPixel(x, y, BLACK)
else -> destination.setPixel(x, y, WHITE)
}
}
}
callback()
}
}
}
class FilterFloydSteinberg : Filter() {
override fun process(source: FilterImage, destination: FilterImage, callback: () -> Unit) {
thread {
val width = source.width
val height = source.height
val errors = Array(width) { IntArray(height) }
for (y in 0 until height - 1) {
for (x in 1 until width - 1) {
val gray = source.getPixel(x, y) shr 16 and 0xFF
var error: Int
when {
gray + errors[x][y] < threshold -> {
error = gray + errors[x][y]
destination.setPixel(x, y, BLACK)
}
else -> {
error = gray + errors[x][y] - 255
destination.setPixel(x, y, WHITE)
}
}
errors[x + 1][y] += 7 * error / 16
errors[x - 1][y + 1] += 3 * error / 16
errors[x][y + 1] += 5 * error / 16
errors[x + 1][y + 1] += 1 * error / 16
}
}
callback()
}
}
}
class FilterJarvisJudiceNinke : Filter() {
override fun process(source: FilterImage, destination: FilterImage, callback: () -> Unit) {
thread {
val width = source.width
val height = source.height
val errors = Array(width) { IntArray(height) }
for (y in 0 until height - 2) {
for (x in 2 until width - 2) {
val gray = source.getPixel(x, y) shr 16 and 0xFF
var error: Int
when {
gray + errors[x][y] < threshold -> {
error = gray + errors[x][y]
destination.setPixel(x, y, BLACK)
}
else -> {
error = gray + errors[x][y] - 255
destination.setPixel(x, y, WHITE)
}
}
errors[x + 1][y] += 7 * error / 48
errors[x + 2][y] += 5 * error / 48
errors[x - 2][y + 1] += 3 * error / 48
errors[x - 1][y + 1] += 5 * error / 48
errors[x][y + 1] += 7 * error / 48
errors[x + 1][y + 1] += 5 * error / 48
errors[x + 2][y + 1] += 3 * error / 48
errors[x - 2][y + 2] += 1 * error / 48
errors[x - 1][y + 2] += 3 * error / 48
errors[x][y + 2] += 5 * error / 48
errors[x + 1][y + 2] += 3 * error / 48
errors[x + 2][y + 2] += 1 * error / 48
}
}
callback()
}
}
}
class FilterSierra : Filter() {
override fun process(source: FilterImage, destination: FilterImage, callback: () -> Unit) {
thread {
val width = source.width
val height = source.height
val errors = Array(width) { IntArray(height) }
for (y in 0 until height - 2) {
for (x in 2 until width - 2) {
val gray = source.getPixel(x, y) shr 16 and 0xFF
var error: Int
when {
gray + errors[x][y] < threshold -> {
error = gray + errors[x][y]
destination.setPixel(x, y, BLACK)
}
else -> {
error = gray + errors[x][y] - 255
destination.setPixel(x, y, WHITE)
}
}
errors[x + 1][y] += 5 * error / 32
errors[x + 2][y] += 3 * error / 32
errors[x - 2][y + 1] += 2 * error / 32
errors[x - 1][y + 1] += 4 * error / 32
errors[x][y + 1] += 5 * error / 32
errors[x + 1][y + 1] += 4 * error / 32
errors[x + 2][y + 1] += 2 * error / 32
errors[x - 1][y + 2] += 2 * error / 32
errors[x][y + 2] += 3 * error / 32
errors[x + 1][y + 2] += 2 * error / 32
}
}
callback()
}
}
}
class FilterTwoRowSierra : Filter() {
override fun process(source: FilterImage, destination: FilterImage, callback: () -> Unit) {
thread {
val width = source.width
val height = source.height
val errors = Array(width) { IntArray(height) }
for (y in 0 until height - 1) {
for (x in 2 until width - 2) {
val gray = source.getPixel(x, y) shr 16 and 0xFF
var error: Int
when {
gray + errors[x][y] < threshold -> {
error = gray + errors[x][y]
destination.setPixel(x, y, BLACK)
}
else -> {
error = gray + errors[x][y] - 255
destination.setPixel(x, y, WHITE)
}
}
errors[x + 1][y] += 4 * error / 16
errors[x + 2][y] += 3 * error / 16
errors[x - 2][y + 1] += 1 * error / 16
errors[x - 1][y + 1] += 2 * error / 16
errors[x][y + 1] += 3 * error / 16
errors[x + 1][y + 1] += 2 * error / 16
errors[x + 2][y + 1] += 1 * error / 16
}
}
callback()
}
}
}
//Incremented by 2X2 each iteration
class FilterSierraLite : Filter() {
override fun process(source: FilterImage, destination: FilterImage, callback: () -> Unit) {
thread {
val width = source.width
val height = source.height
val errors = Array(width) { IntArray(height) }
var y = 0
while (y < height - 1) {
var x = 1
while (x < width - 1) {
val gray = source.getPixel(x, y) shr 16 and 0xFF
var error: Int
when {
gray + errors[x][y] < threshold -> {
error = gray + errors[x][y]
destination.setPixel(x, y, BLACK)
}
else -> {
error = gray + errors[x][y] - 255
destination.setPixel(x, y, WHITE)
}
}
errors[x + 1][y] += 2 * error / 4
errors[x - 1][y + 1] += 1 * error / 4
errors[x][y + 1] += 1 * error / 4
x += 2
}
y += 2
}
callback()
}
}
}
class FilterAtkinson : Filter() {
override fun process(source: FilterImage, destination: FilterImage, callback: () -> Unit) {
thread {
val width = source.width
val height = source.height
val errors = Array(width) { IntArray(height) }
for (y in 0 until height - 2) {
for (x in 1 until width - 2) {
val gray = source.getPixel(x, y) shr 16 and 0xFF
var error: Int
when {
gray + errors[x][y] < threshold -> {
error = gray + errors[x][y]
destination.setPixel(x, y, BLACK)
}
else -> {
error = gray + errors[x][y] - 255
destination.setPixel(x, y, WHITE)
}
}
errors[x + 1][y] += error / 8
errors[x + 2][y] += error / 8
errors[x - 1][y + 1] += error / 8
errors[x][y + 1] += error / 8
errors[x + 1][y + 1] += error / 8
errors[x][y + 2] += error / 8
}
}
callback()
}
}
}
class FilterStucki : Filter() {
override fun process(source: FilterImage, destination: FilterImage, callback: () -> Unit) {
thread {
val width = source.width
val height = source.height
val errors = Array(width) { IntArray(height) }
for (y in 0 until height - 2) {
for (x in 2 until width - 2) {
val gray = source.getPixel(x, y) shr 16 and 0xFF
var error: Int
when {
gray + errors[x][y] < threshold -> {
error = gray + errors[x][y]
destination.setPixel(x, y, BLACK)
}
else -> {
error = gray + errors[x][y] - 255
destination.setPixel(x, y, WHITE)
}
}
errors[x + 1][y] += 8 * error / 42
errors[x + 2][y] += 4 * error / 42
errors[x - 2][y + 1] += 2 * error / 42
errors[x - 1][y + 1] += 4 * error / 42
errors[x][y + 1] += 8 * error / 42
errors[x + 1][y + 1] += 4 * error / 42
errors[x + 2][y + 1] += 2 * error / 42
errors[x - 2][y + 2] += 1 * error / 42
errors[x - 1][y + 2] += 2 * error / 42
errors[x][y + 2] += 4 * error / 42
errors[x + 1][y + 2] += 2 * error / 42
errors[x + 2][y + 2] += 1 * error / 42
}
}
callback()
}
}
}
class FilterBurkes: Filter(){
override fun process(source: FilterImage, destination: FilterImage, callback: () -> Unit) {
thread {
val width = source.width
val height = source.height
val errors = Array(width) { IntArray(height) }
for (y in 0 until height - 1) {
for (x in 2 until width - 2) {
val gray = source.getPixel(x, y) shr 16 and 0xFF
var error: Int
when {
gray + errors[x][y] < threshold -> {
error = gray + errors[x][y]
destination.setPixel(x, y, BLACK)
}
else -> {
error = gray + errors[x][y] - 255
destination.setPixel(x, y, WHITE)
}
}
errors[x + 1][y] += 8 * error / 32
errors[x + 2][y] += 4 * error / 32
errors[x - 2][y + 1] += 2 * error / 32
errors[x - 1][y + 1] += 4 * error / 32
errors[x][y + 1] += 8 * error / 32
errors[x + 1][y + 1] += 4 * error / 32
errors[x + 2][y + 1] += 2 * error / 32
}
}
callback()
}
}
}
class FilterFalseFloydSteinberg: Filter(){
override fun process(source: FilterImage, destination: FilterImage, callback: () -> Unit) {
thread {
val width = source.width
val height = source.height
val errors = Array(width) { IntArray(height) }
for (y in 0 until height - 1) {
for (x in 1 until width - 1) {
val gray = source.getPixel(x, y) shr 16 and 0xFF
var error: Int
when {
gray + errors[x][y] < threshold -> {
error = gray + errors[x][y]
destination.setPixel(x, y, BLACK)
}
else -> {
error = gray + errors[x][y] - 255
destination.setPixel(x, y, WHITE)
}
}
errors[x + 1][y] += 3 * error / 8
errors[x][y + 1] += 3 * error / 8
errors[x + 1][y + 1] += 2 * error / 8
}
}
callback()
}
}
}
class FilterLeftToRightErrorDiffusion: Filter(){
override fun process(source: FilterImage, destination: FilterImage, callback: () -> Unit) {
thread {
val width = source.width
val height = source.height
for (y in 0 until height) {
var error = 0
for (x in 0 until width) {
val gray = source.getPixel(x, y) shr 16 and 0xFF
var delta: Int
when {
gray + error < threshold -> {
delta = gray
destination.setPixel(x, y, BLACK)
}
else -> {
delta = gray - 255
destination.setPixel(x, y, WHITE)
}
}
if (Math.abs(delta) < 10) delta = 0
error += delta
}
}
callback()
}
}
}
class FilterRandom: Filter(){
override fun process(source: FilterImage, destination: FilterImage, callback: () -> Unit) {
thread {
val width = source.width
val height = source.height
for (y in 0 until height) {
for (x in 0 until width) {
val gray = source.getPixel(x, y) shr 16 and 0xFF
val threshold = (Math.random() * 1000).toInt() % 256
when {
gray < threshold -> destination.setPixel(x, y, BLACK)
else -> destination.setPixel(x, y, WHITE)
}
}
}
callback()
}
}
}
class FilterThreshold: Filter(){
override fun process(source: FilterImage, destination: FilterImage, callback: () -> Unit) {
thread {
val width = source.width
val height = source.height
for (y in 0 until height) {
for (x in 0 until width) {
val gray = source.getPixel(x, y) shr 16 and 0xFF
when {
gray < threshold -> destination.setPixel(x, y, BLACK)
else -> destination.setPixel(x, y, WHITE)
}
}
}
callback()
}
}
}