//======================================================================== // // SplashScreen.cc // //======================================================================== //======================================================================== // // Modified under the Poppler project - http://poppler.freedesktop.org // // All changes made under the Poppler project to this file are licensed // under GPL version 2 or later // // Copyright (C) 2009, 2016, 2018, 2020 Albert Astals Cid // Copyright (C) 2012 Fabio D'Urso // // To see a description of the changes please see the Changelog file that // came with your tarball or type make ChangeLog if you are building from git // //======================================================================== #include #include #include #include #include "goo/gmem.h" #include "goo/grandom.h" #include "goo/GooLikely.h" #include "SplashMath.h" #include "SplashScreen.h" static const SplashScreenParams defaultParams = { splashScreenDispersed, // type 2, // size 2, // dotRadius 1.0, // gamma 0.0, // blackThreshold 1.0 // whiteThreshold }; //------------------------------------------------------------------------ struct SplashScreenPoint { int x, y; int dist; }; struct cmpDistancesFunctor { bool operator()(const SplashScreenPoint p0, const SplashScreenPoint p1) { return p0.dist < p1.dist; } }; //------------------------------------------------------------------------ // SplashScreen //------------------------------------------------------------------------ // If is true, this generates a 45 degree screen using a // circular dot spot function. DPI = resolution / ((size / 2) * // sqrt(2)). If is false, this generates an optimal // threshold matrix using recursive tesselation. Gamma correction // (gamma = 1 / 1.33) is also computed here. SplashScreen::SplashScreen(const SplashScreenParams *params) { if (!params) { params = &defaultParams; } screenParams = params; mat = nullptr; size = 0; maxVal = 0; minVal = 0; } void SplashScreen::createMatrix() { unsigned char u; int black, white, i; const SplashScreenParams *params = screenParams; // size must be a power of 2, and at least 2 for (size = 2, log2Size = 1; size < params->size; size <<= 1, ++log2Size) ; switch (params->type) { case splashScreenDispersed: mat = (unsigned char *)gmallocn(size * size, sizeof(unsigned char)); buildDispersedMatrix(size / 2, size / 2, 1, size / 2, 1); break; case splashScreenClustered: mat = (unsigned char *)gmallocn(size * size, sizeof(unsigned char)); buildClusteredMatrix(); break; case splashScreenStochasticClustered: // size must be at least 2*r while (size < (params->dotRadius << 1)) { size <<= 1; ++log2Size; } mat = (unsigned char *)gmallocn(size * size, sizeof(unsigned char)); buildSCDMatrix(params->dotRadius); break; } sizeM1 = size - 1; // do gamma correction and compute minVal/maxVal minVal = 255; maxVal = 0; black = splashRound((SplashCoord)255.0 * params->blackThreshold); if (black < 1) { black = 1; } int whiteAux = splashRound((SplashCoord)255.0 * params->whiteThreshold); if (whiteAux > 255) { white = 255; } else { white = whiteAux; } for (i = 0; i < size * size; ++i) { u = splashRound((SplashCoord)255.0 * splashPow((SplashCoord)mat[i] / 255.0, params->gamma)); if (u < black) { u = (unsigned char)black; } else if (u >= white) { u = (unsigned char)white; } mat[i] = u; if (u < minVal) { minVal = u; } else if (u > maxVal) { maxVal = u; } } } void SplashScreen::buildDispersedMatrix(int i, int j, int val, int delta, int offset) { if (delta == 0) { // map values in [1, size^2] --> [1, 255] mat[(i << log2Size) + j] = 1 + (254 * (val - 1)) / (size * size - 1); } else { buildDispersedMatrix(i, j, val, delta / 2, 4 * offset); buildDispersedMatrix((i + delta) % size, (j + delta) % size, val + offset, delta / 2, 4 * offset); buildDispersedMatrix((i + delta) % size, j, val + 2 * offset, delta / 2, 4 * offset); buildDispersedMatrix((i + 2 * delta) % size, (j + delta) % size, val + 3 * offset, delta / 2, 4 * offset); } } void SplashScreen::buildClusteredMatrix() { SplashCoord *dist; SplashCoord u, v, d; unsigned char val; int size2, x, y, x1, y1, i; size2 = size >> 1; // initialize the threshold matrix for (y = 0; y < size; ++y) { for (x = 0; x < size; ++x) { mat[(y << log2Size) + x] = 0; } } // build the distance matrix dist = (SplashCoord *)gmallocn(size * size2, sizeof(SplashCoord)); for (y = 0; y < size2; ++y) { for (x = 0; x < size2; ++x) { if (x + y < size2 - 1) { u = (SplashCoord)x + 0.5 - 0; v = (SplashCoord)y + 0.5 - 0; } else { u = (SplashCoord)x + 0.5 - (SplashCoord)size2; v = (SplashCoord)y + 0.5 - (SplashCoord)size2; } dist[y * size2 + x] = u * u + v * v; } } for (y = 0; y < size2; ++y) { for (x = 0; x < size2; ++x) { if (x < y) { u = (SplashCoord)x + 0.5 - 0; v = (SplashCoord)y + 0.5 - (SplashCoord)size2; } else { u = (SplashCoord)x + 0.5 - (SplashCoord)size2; v = (SplashCoord)y + 0.5 - 0; } dist[(size2 + y) * size2 + x] = u * u + v * v; } } // build the threshold matrix x1 = y1 = 0; // make gcc happy for (i = 0; i < size * size2; ++i) { d = -1; for (y = 0; y < size; ++y) { for (x = 0; x < size2; ++x) { if (mat[(y << log2Size) + x] == 0 && dist[y * size2 + x] > d) { x1 = x; y1 = y; d = dist[y1 * size2 + x1]; } } } // map values in [0, 2*size*size2-1] --> [1, 255] val = 1 + (254 * (2 * i)) / (2 * size * size2 - 1); mat[(y1 << log2Size) + x1] = val; val = 1 + (254 * (2 * i + 1)) / (2 * size * size2 - 1); if (y1 < size2) { mat[((y1 + size2) << log2Size) + x1 + size2] = val; } else { mat[((y1 - size2) << log2Size) + x1 + size2] = val; } } gfree(dist); } // Compute the distance between two points on a toroid. int SplashScreen::distance(int x0, int y0, int x1, int y1) { int dx0, dx1, dx, dy0, dy1, dy; dx0 = abs(x0 - x1); dx1 = size - dx0; dx = dx0 < dx1 ? dx0 : dx1; dy0 = abs(y0 - y1); dy1 = size - dy0; dy = dy0 < dy1 ? dy0 : dy1; return dx * dx + dy * dy; } // Algorithm taken from: // Victor Ostromoukhov and Roger D. Hersch, "Stochastic Clustered-Dot // Dithering" in Color Imaging: Device-Independent Color, Color // Hardcopy, and Graphic Arts IV, SPIE Vol. 3648, pp. 496-505, 1999. void SplashScreen::buildSCDMatrix(int r) { SplashScreenPoint *dots, *pts; int dotsLen, dotsSize; char *tmpl; char *grid; int *region, *dist; int x, y, xx, yy, x0, x1, y0, y1, i, j, d, iMin, dMin, n; // generate the random space-filling curve pts = (SplashScreenPoint *)gmallocn(size * size, sizeof(SplashScreenPoint)); i = 0; for (y = 0; y < size; ++y) { for (x = 0; x < size; ++x) { pts[i].x = x; pts[i].y = y; ++i; } } for (i = 0; i < size * size; ++i) { j = i + (int)((double)(size * size - i) * grandom_double()); x = pts[i].x; y = pts[i].y; pts[i].x = pts[j].x; pts[i].y = pts[j].y; pts[j].x = x; pts[j].y = y; } // construct the circle template tmpl = (char *)gmallocn((r + 1) * (r + 1), sizeof(char)); for (y = 0; y <= r; ++y) { for (x = 0; x <= r; ++x) { tmpl[y * (r + 1) + x] = (x * y <= r * r) ? 1 : 0; } } // mark all grid cells as free grid = (char *)gmallocn(size * size, sizeof(char)); for (y = 0; y < size; ++y) { for (x = 0; x < size; ++x) { grid[(y << log2Size) + x] = 0; } } // walk the space-filling curve, adding dots dotsLen = 0; dotsSize = 32; dots = (SplashScreenPoint *)gmallocn(dotsSize, sizeof(SplashScreenPoint)); for (i = 0; i < size * size; ++i) { x = pts[i].x; y = pts[i].y; if (!grid[(y << log2Size) + x]) { if (dotsLen == dotsSize) { dotsSize *= 2; dots = (SplashScreenPoint *)greallocn(dots, dotsSize, sizeof(SplashScreenPoint)); } dots[dotsLen++] = pts[i]; for (yy = 0; yy <= r; ++yy) { y0 = (y + yy) % size; y1 = (y - yy + size) % size; for (xx = 0; xx <= r; ++xx) { if (tmpl[yy * (r + 1) + xx]) { x0 = (x + xx) % size; x1 = (x - xx + size) % size; grid[(y0 << log2Size) + x0] = 1; grid[(y0 << log2Size) + x1] = 1; grid[(y1 << log2Size) + x0] = 1; grid[(y1 << log2Size) + x1] = 1; } } } } } gfree(tmpl); gfree(grid); // assign each cell to a dot, compute distance to center of dot region = (int *)gmallocn(size * size, sizeof(int)); dist = (int *)gmallocn(size * size, sizeof(int)); for (y = 0; y < size; ++y) { for (x = 0; x < size; ++x) { iMin = 0; dMin = distance(dots[0].x, dots[0].y, x, y); for (i = 1; i < dotsLen; ++i) { d = distance(dots[i].x, dots[i].y, x, y); if (d < dMin) { iMin = i; dMin = d; } } region[(y << log2Size) + x] = iMin; dist[(y << log2Size) + x] = dMin; } } // compute threshold values for (i = 0; i < dotsLen; ++i) { n = 0; for (y = 0; y < size; ++y) { for (x = 0; x < size; ++x) { if (region[(y << log2Size) + x] == i) { pts[n].x = x; pts[n].y = y; pts[n].dist = distance(dots[i].x, dots[i].y, x, y); ++n; } } } std::sort(pts, pts + n, cmpDistancesFunctor()); for (j = 0; j < n; ++j) { // map values in [0 .. n-1] --> [255 .. 1] mat[(pts[j].y << log2Size) + pts[j].x] = 255 - (254 * j) / (n - 1); } } gfree(pts); gfree(region); gfree(dist); gfree(dots); } SplashScreen::SplashScreen(SplashScreen *screen) { screenParams = screen->screenParams; size = screen->size; sizeM1 = screen->sizeM1; log2Size = screen->log2Size; mat = (unsigned char *)gmallocn(size * size, sizeof(unsigned char)); if (likely(mat != nullptr)) { memcpy(mat, screen->mat, size * size * sizeof(unsigned char)); } minVal = screen->minVal; maxVal = screen->maxVal; } SplashScreen::~SplashScreen() { gfree(mat); }