|
4 | 4 | // GetBitmapHeaderSize |
5 | 5 | // |
6 | 6 | #include "bitmapUtilities.h" |
| 7 | +#include <algorithm> |
7 | 8 |
|
8 | 9 | ULONG GetBitmapHeaderSize(LPCVOID pDib) |
9 | 10 | { |
@@ -663,6 +664,55 @@ bool dithering_image(const ARGB* pixels, const ColorPalette* pPalette, DitherFn |
663 | 664 | return true; |
664 | 665 | } |
665 | 666 |
|
| 667 | +// Standard Interleaved Gradient Noise formula by Jorge Jimenez |
| 668 | +inline float GetInterleavedGradientNoise(int x, int y) |
| 669 | +{ |
| 670 | + // Returns a pseudo-random value between 0.0f and 1.0f |
| 671 | + return fmod(52.9829189f * fmod(0.06711056f * float(x) + 0.00583715f * float(y), 1.0f), 1.0f); |
| 672 | +} |
| 673 | + |
| 674 | +bool dither_image(const ARGB* pixels, const ARGB* pPalette, const UINT nMaxColors, DitherFn ditherFn, |
| 675 | + const bool& hasSemiTransparency, const int& transparentPixelIndex, unsigned short* qPixels, |
| 676 | + const UINT width, const UINT height, const vector<float>& saliencies, bool enforcedDither) |
| 677 | +{ |
| 678 | + // Base spread determined by palette density |
| 679 | + const float baseSpread = 255.0f / cbrt(static_cast<float>(nMaxColors)); |
| 680 | + |
| 681 | + for (UINT y = 0; y < height; ++y) |
| 682 | + { |
| 683 | + for (UINT x = 0; x < width; ++x) |
| 684 | + { |
| 685 | + UINT pixelIndex = y * width + x; |
| 686 | + Color c(pixels[pixelIndex]); |
| 687 | + |
| 688 | + // Handle pure transparency early |
| 689 | + if (transparentPixelIndex >= 0 && c.GetA() == 0) { |
| 690 | + qPixels[pixelIndex] = static_cast<unsigned short>(transparentPixelIndex); |
| 691 | + continue; |
| 692 | + } |
| 693 | + |
| 694 | + // Generate centered noise [-0.5, 0.5] |
| 695 | + float noise = GetInterleavedGradientNoise(static_cast<int>(x), static_cast<int>(y)) - 0.5f; |
| 696 | + |
| 697 | + // Compute noise offset. If saliencies, modulate by pixel saliency; |
| 698 | + // otherwise, use uniform base noise intensity across the image. |
| 699 | + float weight = enforcedDither && !saliencies.empty() ? saliencies[pixelIndex] : 1.0f; |
| 700 | + float offset = noise * baseSpread * weight; |
| 701 | + |
| 702 | + // Apply noise and clamp safely to RGB limits |
| 703 | + int r = clamp(static_cast<int>(c.GetR() + offset), 0, 255); |
| 704 | + int g = clamp(static_cast<int>(c.GetG() + offset), 0, 255); |
| 705 | + int b = clamp(static_cast<int>(c.GetB() + offset), 0, 255); |
| 706 | + int a = c.GetA(); |
| 707 | + |
| 708 | + auto noisyArgb = Color::MakeARGB(a, r, g, b); |
| 709 | + qPixels[pixelIndex] = ditherFn(pPalette, nMaxColors, noisyArgb, y + x); |
| 710 | + } |
| 711 | + } |
| 712 | + |
| 713 | + return true; |
| 714 | +} |
| 715 | + |
666 | 716 | bool ProcessImagePixels(Bitmap* pDest, const ARGB* qPixels, const bool& hasSemiTransparency, const int& transparentPixelIndex) |
667 | 717 | { |
668 | 718 | UINT bpp = GetPixelFormatSize(pDest->GetPixelFormat()); |
|
0 commit comments