1#include "image_manipulation.h"
2
3#include <base/dbg.h>
4#include <base/math.h>
5#include <base/mem.h>
6
7#include <cstdlib>
8
9bool ConvertToRgba(uint8_t *pDest, const CImageInfo &SourceImage)
10{
11 if(SourceImage.m_Format == CImageInfo::FORMAT_RGBA)
12 {
13 mem_copy(dest: pDest, source: SourceImage.m_pData, size: SourceImage.DataSize());
14 return true;
15 }
16 else
17 {
18 const size_t SrcChannelCount = CImageInfo::PixelSize(Format: SourceImage.m_Format);
19 const size_t DstChannelCount = CImageInfo::PixelSize(Format: CImageInfo::FORMAT_RGBA);
20 for(size_t Y = 0; Y < SourceImage.m_Height; ++Y)
21 {
22 for(size_t X = 0; X < SourceImage.m_Width; ++X)
23 {
24 size_t ImgOffsetSrc = (Y * SourceImage.m_Width * SrcChannelCount) + (X * SrcChannelCount);
25 size_t ImgOffsetDest = (Y * SourceImage.m_Width * DstChannelCount) + (X * DstChannelCount);
26 if(SourceImage.m_Format == CImageInfo::FORMAT_RGB)
27 {
28 mem_copy(dest: &pDest[ImgOffsetDest], source: &SourceImage.m_pData[ImgOffsetSrc], size: SrcChannelCount);
29 pDest[ImgOffsetDest + 3] = 255;
30 }
31 else if(SourceImage.m_Format == CImageInfo::FORMAT_RA)
32 {
33 pDest[ImgOffsetDest + 0] = SourceImage.m_pData[ImgOffsetSrc];
34 pDest[ImgOffsetDest + 1] = SourceImage.m_pData[ImgOffsetSrc];
35 pDest[ImgOffsetDest + 2] = SourceImage.m_pData[ImgOffsetSrc];
36 pDest[ImgOffsetDest + 3] = SourceImage.m_pData[ImgOffsetSrc + 1];
37 }
38 else if(SourceImage.m_Format == CImageInfo::FORMAT_R)
39 {
40 pDest[ImgOffsetDest + 0] = 255;
41 pDest[ImgOffsetDest + 1] = 255;
42 pDest[ImgOffsetDest + 2] = 255;
43 pDest[ImgOffsetDest + 3] = SourceImage.m_pData[ImgOffsetSrc];
44 }
45 else
46 {
47 dbg_assert_failed("SourceImage.m_Format invalid");
48 }
49 }
50 }
51 return false;
52 }
53}
54
55bool ConvertToRgbaAlloc(uint8_t *&pDest, const CImageInfo &SourceImage)
56{
57 pDest = static_cast<uint8_t *>(malloc(size: SourceImage.m_Width * SourceImage.m_Height * CImageInfo::PixelSize(Format: CImageInfo::FORMAT_RGBA)));
58 return ConvertToRgba(pDest, SourceImage);
59}
60
61bool ConvertToRgba(CImageInfo &Image)
62{
63 if(Image.m_Format == CImageInfo::FORMAT_RGBA)
64 return true;
65
66 uint8_t *pRgbaData;
67 ConvertToRgbaAlloc(pDest&: pRgbaData, SourceImage: Image);
68 free(ptr: Image.m_pData);
69 Image.m_pData = pRgbaData;
70 Image.m_Format = CImageInfo::FORMAT_RGBA;
71 return false;
72}
73
74void ConvertToGrayscale(const CImageInfo &Image)
75{
76 if(Image.m_Format == CImageInfo::FORMAT_R || Image.m_Format == CImageInfo::FORMAT_RA)
77 return;
78
79 const size_t Step = Image.PixelSize();
80 for(size_t i = 0; i < Image.m_Width * Image.m_Height; ++i)
81 {
82 const uint8_t R = Image.m_pData[i * Step];
83 const uint8_t G = Image.m_pData[i * Step + 1];
84 const uint8_t B = Image.m_pData[i * Step + 2];
85 const uint8_t Luma = (uint8_t)(0.2126f * R + 0.7152f * G + 0.0722f * B);
86
87 Image.m_pData[i * Step] = Luma;
88 Image.m_pData[i * Step + 1] = Luma;
89 Image.m_pData[i * Step + 2] = Luma;
90 }
91}
92
93static constexpr int DILATE_BPP = 4; // RGBA assumed
94static constexpr uint8_t DILATE_ALPHA_THRESHOLD = 10;
95
96static void Dilate(int w, int h, const uint8_t *pSrc, uint8_t *pDest)
97{
98 const int aDirX[] = {0, -1, 1, 0};
99 const int aDirY[] = {-1, 0, 0, 1};
100
101 int m = 0;
102 for(int y = 0; y < h; y++)
103 {
104 for(int x = 0; x < w; x++, m += DILATE_BPP)
105 {
106 for(int i = 0; i < DILATE_BPP; ++i)
107 pDest[m + i] = pSrc[m + i];
108 if(pSrc[m + DILATE_BPP - 1] > DILATE_ALPHA_THRESHOLD)
109 continue;
110
111 // --- Implementation Note ---
112 // The sum and counter variable can be used to compute a smoother dilated image.
113 // In this reference implementation, the loop breaks as soon as Counter == 1.
114 // We break the loop here to match the selection of the previously used algorithm.
115 int aSumOfOpaque[] = {0, 0, 0};
116 int Counter = 0;
117 for(int c = 0; c < 4; c++)
118 {
119 const int ClampedX = std::clamp(val: x + aDirX[c], lo: 0, hi: w - 1);
120 const int ClampedY = std::clamp(val: y + aDirY[c], lo: 0, hi: h - 1);
121 const int SrcIndex = ClampedY * w * DILATE_BPP + ClampedX * DILATE_BPP;
122 if(pSrc[SrcIndex + DILATE_BPP - 1] > DILATE_ALPHA_THRESHOLD)
123 {
124 for(int p = 0; p < DILATE_BPP - 1; ++p)
125 aSumOfOpaque[p] += pSrc[SrcIndex + p];
126 ++Counter;
127 break;
128 }
129 }
130
131 if(Counter > 0)
132 {
133 for(int i = 0; i < DILATE_BPP - 1; ++i)
134 {
135 aSumOfOpaque[i] /= Counter;
136 pDest[m + i] = (uint8_t)aSumOfOpaque[i];
137 }
138
139 pDest[m + DILATE_BPP - 1] = 255;
140 }
141 }
142 }
143}
144
145static void CopyColorValues(int w, int h, const uint8_t *pSrc, uint8_t *pDest)
146{
147 int m = 0;
148 for(int y = 0; y < h; y++)
149 {
150 for(int x = 0; x < w; x++, m += DILATE_BPP)
151 {
152 if(pDest[m + DILATE_BPP - 1] == 0)
153 {
154 mem_copy(dest: &pDest[m], source: &pSrc[m], size: DILATE_BPP - 1);
155 }
156 }
157 }
158}
159
160void DilateImage(uint8_t *pImageBuff, int w, int h)
161{
162 DilateImageSub(pImageBuff, w, h, x: 0, y: 0, SubWidth: w, SubHeight: h);
163}
164
165void DilateImage(const CImageInfo &Image)
166{
167 dbg_assert(Image.m_Format == CImageInfo::FORMAT_RGBA, "Dilate requires RGBA format");
168 DilateImage(pImageBuff: Image.m_pData, w: Image.m_Width, h: Image.m_Height);
169}
170
171void DilateImageSub(uint8_t *pImageBuff, int w, int h, int x, int y, int SubWidth, int SubHeight)
172{
173 uint8_t *apBuffer[2] = {nullptr, nullptr};
174
175 const size_t ImageSize = (size_t)SubWidth * SubHeight * sizeof(uint8_t) * DILATE_BPP;
176 apBuffer[0] = (uint8_t *)malloc(size: ImageSize);
177 apBuffer[1] = (uint8_t *)malloc(size: ImageSize);
178 uint8_t *pBufferOriginal = (uint8_t *)malloc(size: ImageSize);
179
180 for(int Y = 0; Y < SubHeight; ++Y)
181 {
182 int SrcImgOffset = ((y + Y) * w * DILATE_BPP) + (x * DILATE_BPP);
183 int DstImgOffset = (Y * SubWidth * DILATE_BPP);
184 int CopySize = SubWidth * DILATE_BPP;
185 mem_copy(dest: &pBufferOriginal[DstImgOffset], source: &pImageBuff[SrcImgOffset], size: CopySize);
186 }
187
188 Dilate(w: SubWidth, h: SubHeight, pSrc: pBufferOriginal, pDest: apBuffer[0]);
189
190 for(int i = 0; i < 5; i++)
191 {
192 Dilate(w: SubWidth, h: SubHeight, pSrc: apBuffer[0], pDest: apBuffer[1]);
193 Dilate(w: SubWidth, h: SubHeight, pSrc: apBuffer[1], pDest: apBuffer[0]);
194 }
195
196 CopyColorValues(w: SubWidth, h: SubHeight, pSrc: apBuffer[0], pDest: pBufferOriginal);
197
198 free(ptr: apBuffer[0]);
199 free(ptr: apBuffer[1]);
200
201 for(int Y = 0; Y < SubHeight; ++Y)
202 {
203 int SrcImgOffset = ((y + Y) * w * DILATE_BPP) + (x * DILATE_BPP);
204 int DstImgOffset = (Y * SubWidth * DILATE_BPP);
205 int CopySize = SubWidth * DILATE_BPP;
206 mem_copy(dest: &pImageBuff[SrcImgOffset], source: &pBufferOriginal[DstImgOffset], size: CopySize);
207 }
208
209 free(ptr: pBufferOriginal);
210}
211
212static float CubicHermite(float A, float B, float C, float D, float t)
213{
214 float a = -A / 2.0f + (3.0f * B) / 2.0f - (3.0f * C) / 2.0f + D / 2.0f;
215 float b = A - (5.0f * B) / 2.0f + 2.0f * C - D / 2.0f;
216 float c = -A / 2.0f + C / 2.0f;
217 float d = B;
218
219 return (a * t * t * t) + (b * t * t) + (c * t) + d;
220}
221
222static void GetPixelClamped(const uint8_t *pSourceImage, int x, int y, uint32_t W, uint32_t H, size_t BPP, uint8_t aSample[4])
223{
224 x = std::clamp<int>(val: x, lo: 0, hi: (int)W - 1);
225 y = std::clamp<int>(val: y, lo: 0, hi: (int)H - 1);
226
227 mem_copy(dest: aSample, source: &pSourceImage[x * BPP + (W * BPP * y)], size: BPP);
228}
229
230static void SampleBicubic(const uint8_t *pSourceImage, float u, float v, uint32_t W, uint32_t H, size_t BPP, uint8_t aSample[4])
231{
232 float X = (u * W) - 0.5f;
233 const int RoundedX = (int)X;
234 const float FractionX = X - std::floor(x: X);
235
236 float Y = (v * H) - 0.5f;
237 const int RoundedY = (int)Y;
238 const float FractionY = Y - std::floor(x: Y);
239
240 uint8_t aaaSamples[4][4][4];
241 for(int y = 0; y < 4; ++y)
242 {
243 for(int x = 0; x < 4; ++x)
244 {
245 GetPixelClamped(pSourceImage, x: RoundedX + x - 1, y: RoundedY + y - 1, W, H, BPP, aSample: aaaSamples[x][y]);
246 }
247 }
248
249 for(size_t i = 0; i < BPP; i++)
250 {
251 float aRows[4];
252 for(int y = 0; y < 4; ++y)
253 {
254 aRows[y] = CubicHermite(A: aaaSamples[0][y][i], B: aaaSamples[1][y][i], C: aaaSamples[2][y][i], D: aaaSamples[3][y][i], t: FractionX);
255 }
256 aSample[i] = (uint8_t)std::clamp<float>(val: CubicHermite(A: aRows[0], B: aRows[1], C: aRows[2], D: aRows[3], t: FractionY), lo: 0.0f, hi: 255.0f);
257 }
258}
259
260static void ResizeImage(const uint8_t *pSourceImage, uint32_t SW, uint32_t SH, uint8_t *pDestinationImage, uint32_t W, uint32_t H, size_t BPP)
261{
262 for(int y = 0; y < (int)H; ++y)
263 {
264 float v = (float)y / (float)(H - 1);
265 for(int x = 0; x < (int)W; ++x)
266 {
267 float u = (float)x / (float)(W - 1);
268 uint8_t aSample[4];
269 SampleBicubic(pSourceImage, u, v, W: SW, H: SH, BPP, aSample);
270 mem_copy(dest: &pDestinationImage[x * BPP + ((W * BPP) * y)], source: aSample, size: BPP);
271 }
272 }
273}
274
275uint8_t *ResizeImage(const uint8_t *pImageData, int Width, int Height, int NewWidth, int NewHeight, int BPP)
276{
277 uint8_t *pTmpData = (uint8_t *)malloc(size: (size_t)NewWidth * NewHeight * BPP);
278 ResizeImage(pSourceImage: pImageData, SW: Width, SH: Height, pDestinationImage: pTmpData, W: NewWidth, H: NewHeight, BPP);
279 return pTmpData;
280}
281
282void ResizeImage(CImageInfo &Image, int NewWidth, int NewHeight)
283{
284 uint8_t *pNewData = ResizeImage(pImageData: Image.m_pData, Width: Image.m_Width, Height: Image.m_Height, NewWidth, NewHeight, BPP: Image.PixelSize());
285 free(ptr: Image.m_pData);
286 Image.m_pData = pNewData;
287 Image.m_Width = NewWidth;
288 Image.m_Height = NewHeight;
289}
290
291int HighestBit(int OfVar)
292{
293 if(!OfVar)
294 return 0;
295
296 int RetV = 1;
297
298 while(OfVar >>= 1)
299 RetV <<= 1;
300
301 return RetV;
302}
303