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