| @@ -1,7 +1,7 @@ | |||||
| // | // | ||||
| // Lol Engine | // Lol Engine | ||||
| // | // | ||||
| // Copyright: (c) 2004-2014 Sam Hocevar <sam@hocevar.net> | |||||
| // Copyright: (c) 2004-2015 Sam Hocevar <sam@hocevar.net> | |||||
| // This program is free software; you can redistribute it and/or | // This program is free software; you can redistribute it and/or | ||||
| // modify it under the terms of the Do What The Fuck You Want To | // modify it under the terms of the Do What The Fuck You Want To | ||||
| // Public License, Version 2, as published by Sam Hocevar. See | // Public License, Version 2, as published by Sam Hocevar. See | ||||
| @@ -47,159 +47,135 @@ Image Image::DitherDbs() const | |||||
| /* A list of cells in our picture. If no change is done to a cell | /* A list of cells in our picture. If no change is done to a cell | ||||
| * for two iterations, we stop considering changes to it. */ | * for two iterations, we stop considering changes to it. */ | ||||
| ivec2 csize = (size + ivec2(CELL - 1)) / CELL; | |||||
| array2d<int> changelist; | |||||
| changelist.SetSize(csize); | |||||
| ivec2 const csize = (size + ivec2(CELL - 1)) / CELL; | |||||
| array2d<int> changelist(csize); | |||||
| memset(changelist.Data(), 0, changelist.Bytes()); | memset(changelist.Data(), 0, changelist.Bytes()); | ||||
| Image dst = *this; | Image dst = *this; | ||||
| dst.SetFormat(PixelFormat::Y_F32); | dst.SetFormat(PixelFormat::Y_F32); | ||||
| Image tmp1 = dst.Convolution(kernel); | Image tmp1 = dst.Convolution(kernel); | ||||
| float *tmp1data = tmp1.Lock<PixelFormat::Y_F32>(); | |||||
| array2d<float> &tmp1data = tmp1.Lock2D<PixelFormat::Y_F32>(); | |||||
| dst = dst.DitherRandom(); | dst = dst.DitherRandom(); | ||||
| float *dstdata = dst.Lock<PixelFormat::Y_F32>(); | |||||
| array2d<float> &dstdata = dst.Lock2D<PixelFormat::Y_F32>(); | |||||
| Image tmp2 = dst.Convolution(kernel); | Image tmp2 = dst.Convolution(kernel); | ||||
| float *tmp2data = tmp2.Lock<PixelFormat::Y_F32>(); | |||||
| array2d<float> &tmp2data = tmp2.Lock2D<PixelFormat::Y_F32>(); | |||||
| for (;;) | |||||
| for (int run = 0, last_change = 0; ; ++run) | |||||
| { | { | ||||
| int allchanges = 0; | |||||
| int const cell = run % (csize.x * csize.y); | |||||
| int const cx = cell % csize.x; | |||||
| int const cy = cell / csize.x; | |||||
| for (int cy = 0; cy < csize.y; ++cy) | |||||
| for (int cx = 0; cx < csize.x; ++cx) | |||||
| /* Bail out if no change was done for the last full image run */ | |||||
| if (run > last_change + csize.x * csize.y) | |||||
| break; | |||||
| /* Skip cell if it was already ignored twice */ | |||||
| if (changelist[cx][cy] >= 2) | |||||
| continue; | |||||
| int changes = 0; | |||||
| for (int pixel = 0; pixel < CELL * CELL; ++pixel) | |||||
| { | { | ||||
| int changes = 0; | |||||
| ivec2 const pos(cx * CELL + pixel % CELL, | |||||
| cy * CELL + pixel / CELL); | |||||
| if (changelist[cx][cy] >= 2) | |||||
| if (!(pos >= ivec2(0)) || !(pos < size)) | |||||
| continue; | continue; | ||||
| for (int y = cy * CELL; y < (cy + 1) * CELL; ++y) | |||||
| for (int x = cx * CELL; x < (cx + 1) * CELL; ++x) | |||||
| /* The best operation we can do */ | |||||
| ivec2 best_op(0); | |||||
| float best_error = 0.f; | |||||
| float d = dstdata[pos]; | |||||
| /* Compute the effect of all possible toggle and swaps */ | |||||
| static ivec2 const op_list[] = | |||||
| { | |||||
| { 0, 0 }, | |||||
| { 0, 1 }, { 0, -1 }, { -1, 0 }, { 1, 0 }, | |||||
| { -1, -1 }, { -1, 1 }, { 1, -1 }, { 1, 1 }, | |||||
| }; | |||||
| for (ivec2 const op : op_list) | |||||
| { | { | ||||
| int opx = -1, opy = -1; | |||||
| if (!(pos + op >= ivec2(0)) || !(pos + op < size)) | |||||
| continue; | |||||
| float d = dstdata[y * size.x + x]; | |||||
| float d2; | |||||
| bool flip = (op == ivec2(0)); | |||||
| /* Compute the effect of a toggle */ | |||||
| float e = 0.f, best = 0.f; | |||||
| for (int j = -N; j < N + 1; j++) | |||||
| { | |||||
| if (y + j < 0 || y + j >= size.y) | |||||
| continue; | |||||
| for (int i = -N; i < N + 1; i++) | |||||
| { | |||||
| if (x + i < 0 || x + i >= size.x) | |||||
| continue; | |||||
| float m = kernel[i + N][j + N]; | |||||
| float p = tmp1data[(y + j) * size.x + x + i]; | |||||
| float q1 = tmp2data[(y + j) * size.x + x + i]; | |||||
| float q2 = q1 - m * d + m * (1. - d); | |||||
| e += (q1 - p) * (q1 - p) - (q2 - p) * (q2 - p); | |||||
| } | |||||
| } | |||||
| float d2 = flip ? 1 - d : dstdata[pos + op]; | |||||
| if (!flip && d2 == d) | |||||
| continue; | |||||
| /* TODO: implement min/max for 3+ arguments */ | |||||
| int imin = max(max(-N, op.x - N), -pos.x); | |||||
| int imax = min(min(N + 1, op.x + NN - N), size.x - pos.x); | |||||
| int jmin = max(max(-N, op.y - N), -pos.y); | |||||
| int jmax = min(min(N + 1, op.y + NN - N), size.y - pos.y); | |||||
| if (e > best) | |||||
| float error = 0.f; | |||||
| for (int j = jmin; j < jmax; j++) | |||||
| for (int i = imin; i < imax; i++) | |||||
| { | { | ||||
| best = e; | |||||
| opx = opy = 0; | |||||
| ivec2 pos2 = pos + ivec2(i, j); | |||||
| float m = kernel[i + N][j + N]; | |||||
| if (!flip) | |||||
| m -= kernel[i - op.x + N][j - op.y + N]; | |||||
| float p = tmp1data[pos2]; | |||||
| float q1 = tmp2data[pos2]; | |||||
| float q2 = q1 + m * (d2 - d); | |||||
| error += sq(q1 - p) - sq(q2 - p); | |||||
| } | } | ||||
| /* Compute the effect of swaps */ | |||||
| for (int n = 0; n < 8; n++) | |||||
| if (error > best_error) | |||||
| { | { | ||||
| static int const step[] = | |||||
| { 0, 1, 0, -1, -1, 0, 1, 0, -1, -1, -1, 1, 1, -1, 1, 1 }; | |||||
| int idx = step[n * 2], idy = step[n * 2 + 1]; | |||||
| if (y + idy < 0 || y + idy >= size.y | |||||
| || x + idx < 0 || x + idx >= size.x) | |||||
| continue; | |||||
| d2 = dstdata[(y + idy) * size.x + x + idx]; | |||||
| if (d2 == d) | |||||
| continue; | |||||
| e = 0.; | |||||
| for (int j = -N; j < N + 1; j++) | |||||
| { | |||||
| if (y + j < 0 || y + j >= size.y) | |||||
| continue; | |||||
| if (j - idy + N < 0 || j - idy + N >= NN) | |||||
| continue; | |||||
| for (int i = -N; i < N + 1; i++) | |||||
| { | |||||
| if (x + i < 0 || x + i >= size.x) | |||||
| continue; | |||||
| if (i - idx + N < 0 || i - idx + N >= NN) | |||||
| continue; | |||||
| float ma = kernel[i + N][j + N]; | |||||
| float mb = kernel[i - idx + N][j - idy + N]; | |||||
| float p = tmp1data[(y + j) * size.x + x + i]; | |||||
| float q1 = tmp2data[(y + j) * size.x + x + i]; | |||||
| float q2 = q1 - ma * d + ma * d2 - mb * d2 + mb * d; | |||||
| e += (q1 - p) * (q1 - p) - (q2 - p) * (q2 - p); | |||||
| } | |||||
| } | |||||
| if (e > best) | |||||
| { | |||||
| best = e; | |||||
| opx = idx; | |||||
| opy = idy; | |||||
| } | |||||
| best_error = error; | |||||
| best_op = op; | |||||
| } | } | ||||
| } | |||||
| /* Apply the change if interesting */ | |||||
| if (best <= 0.f) | |||||
| continue; | |||||
| /* Only apply the change if interesting */ | |||||
| if (best_error > 0.f) | |||||
| { | |||||
| bool flip = (best_op == ivec2(0)); | |||||
| if (opx || opy) | |||||
| { | |||||
| d2 = dstdata[(y + opy) * size.x + x + opx]; | |||||
| dstdata[(y + opy) * size.x + x + opx] = d; | |||||
| } | |||||
| else | |||||
| d2 = 1. - d; | |||||
| dstdata[y * size.x + x] = d2; | |||||
| float d2 = flip ? 1 - d : dstdata[pos + best_op]; | |||||
| dstdata[pos + best_op] = d; | |||||
| dstdata[pos] = d2; | |||||
| for (int j = -N; j < N + 1; j++) | |||||
| for (int i = -N; i < N + 1; i++) | |||||
| for (int j = -N; j <= N; j++) | |||||
| for (int i = -N; i <= N; i++) | |||||
| { | { | ||||
| float m = kernel[i + N][j + N]; | |||||
| if (y + j >= 0 && y + j < size.y | |||||
| && x + i >= 0 && x + i < size.x) | |||||
| { | |||||
| t = tmp2data[(y + j) * size.x + x + i]; | |||||
| tmp2data[(y + j) * size.x + x + i] = t + m * (d2 - d); | |||||
| } | |||||
| if ((opx || opy) && y + opy + j >= 0 && y + opy + j < size.y | |||||
| && x + opx + i >= 0 && x + opx + i < size.x) | |||||
| { | |||||
| t = tmp2data[(y + opy + j) * size.x + x + opx + i]; | |||||
| tmp2data[(y + opy + j) * size.x + x + opx + i] | |||||
| = t + m * (d - d2); | |||||
| } | |||||
| } | |||||
| ivec2 off(i, j); | |||||
| float delta = (d2 - d) * kernel[i + N][j + N]; | |||||
| changes++; | |||||
| } | |||||
| if (pos + off >= ivec2(0) && pos + off < size) | |||||
| tmp2data[pos + off] += delta; | |||||
| if (changes == 0) | |||||
| ++changelist[cx][cy]; | |||||
| if (!flip && pos + off + best_op >= ivec2(0) | |||||
| && pos + off + best_op < size) | |||||
| tmp2data[pos + off + best_op] -= delta; | |||||
| } | |||||
| allchanges += changes; | |||||
| ++changes; | |||||
| last_change = run; | |||||
| } | |||||
| } | } | ||||
| if (allchanges == 0) | |||||
| break; | |||||
| if (changes == 0) | |||||
| ++changelist[cx][cy]; | |||||
| } | } | ||||
| tmp1.Unlock(tmp1data); | |||||
| tmp2.Unlock(tmp2data); | |||||
| dst.Unlock(dstdata); | |||||
| tmp1.Unlock2D(tmp1data); | |||||
| tmp2.Unlock2D(tmp2data); | |||||
| dst.Unlock2D(dstdata); | |||||
| return dst; | return dst; | ||||
| } | } | ||||