Euphoria
image.cc
Go to the documentation of this file.
1 #include "core/image.h"
2 
3 #include <iostream>
4 
5 #include "stb_image.h"
6 #include "stb_image_write.h"
7 
8 #include "assert/assert.h"
9 #include "base/numeric.h"
10 #include "log/log.h"
11 #include "base/cint.h"
12 #include "core/decompress.h"
13 #include "io/vfs.h"
14 #include "io/vfs_path.h"
15 
16 
17 namespace eu::core
18 {
19  void
21  {
22  components.resize(0);
23  width = 0;
24  height = 0;
25  has_alpha = false;
26 
27  ASSERT(!is_valid());
28  }
29 
30 
31  int
33  {
34  return has_alpha ? 4 : 3;
35  }
36 
37 
38  void
40  (
41  int image_width,
42  int image_height,
43  int default_value
44  )
45  {
46  setup(image_width, image_height, true, default_value);
47  }
48 
49 
50  void
52  (
53  int image_width,
54  int image_height,
55  int default_value
56  )
57  {
58  setup(image_width, image_height, false, default_value);
59  }
60 
61  void
63  (
64  int image_width,
65  int image_height,
66  bool alpha,
67  int default_value
68  )
69  {
70  ASSERTX(image_width > 0, image_width);
71  ASSERTX(image_height > 0, image_height);
72 
73  width = image_width;
74  height = image_height;
75  has_alpha = alpha;
76 
77  components.resize(0); // clear all pixels
78  const unsigned int size = width * height * get_pixel_byte_size();
79  if(default_value < 0)
80  {
81  components.resize(size);
82  }
83  else
84  {
85  ASSERT(default_value <= 255);
86  components.resize(size, static_cast<unsigned char>(default_value));
87  }
88 
89  ASSERT(is_valid());
90  }
91 
92 
93  int
94  Image::get_pixel_index(int x, int y) const
95  {
96  ASSERT(x >= 0 && x < width);
97  ASSERT(y >= 0 && y < height);
98 
99  return (y * width + x) * get_pixel_byte_size();
100  }
101 
102 
103  void
104  Image::set_pixel(int x, int y, const Rgbai& color)
105  {
106  set_pixel(x, y, color.r, color.g, color.b, color.a);
107  }
108 
109 
110  void
112  (
113  int x,
114  int y,
115  unsigned char r,
116  unsigned char g,
117  unsigned char b,
118  unsigned char a
119  )
120  {
123 
124  const auto base_index = get_pixel_index(x, y);
125  components[base_index + 0] = r;
126  components[base_index + 1] = g;
127  components[base_index + 2] = b;
128 
129  if(has_alpha)
130  {
131  components[base_index + 3] = a;
132  }
133  }
134 
135 
136  Rgbai
137  Image::get_pixel(int x, int y) const
138  {
141 
142  const auto base_index = get_pixel_index(x, y);
143 
144  const auto red = components[base_index + 0];
145  const auto green = components[base_index + 1];
146  const auto blue = components[base_index + 2];
147 
148  if(has_alpha)
149  {
150  const auto alpha = components[base_index + 3];
151  return Rgbai{Rgbi{red, green, blue}, alpha};
152  }
153  else
154  {
155  return Rgbi{red, green, blue};
156  }
157  }
158 
159 
160  bool
162  {
163  return width > 0 && height > 0;
164  }
165 
166 
167  Recti
169  {
171  (
172  width - 1,
173  height - 1
174  );
175  }
176 
177  const unsigned char*
179  {
180  return components.data();
181  }
182 
183  namespace
184  {
185  void
186  determine_image_size(void* context, void* /*unused*/, int size)
187  {
188  ASSERT(size >= 0);
189  auto* total_size = static_cast<int*>(context);
190  *total_size += size;
191  }
192 
193  void
194  write_memorychunk_to_file(void* context, void* data, int size)
195  {
196  ASSERT(size >= 0);
197  auto* file = static_cast<MemoryChunkFile*>(context);
198  file->write(data, size);
199  }
200  }
201 
202  int
204  (
205  stbi_write_func* func,
206  void* context,
207  int w,
208  int h,
209  int comp,
210  const void* data,
211  ImageWriteFormat format,
212  int jpeg_quality
213  )
214  {
215  switch(format)
216  {
218  return stbi_write_png_to_func(func, context, w, h, comp, data, 0);
220  return stbi_write_bmp_to_func(func, context, w, h, comp, data);
222  return stbi_write_tga_to_func(func, context, w, h, comp, data);
224  return stbi_write_jpg_to_func
225  (
226  func, context, w, h, comp, data, jpeg_quality
227  );
228  default: DIE("Unhandled case"); return 0;
229  }
230  }
231 
232  std::shared_ptr<MemoryChunk>
233  Image::write(ImageWriteFormat format, int jpeg_quality) const
234  {
235  const int number_of_components = has_alpha ? 4 : 3;
236 
237  const auto pixels_count = c_int_to_sizet(width) * c_int_to_sizet(height);
238  std::vector<unsigned char> pixels(pixels_count * c_int_to_sizet(number_of_components), 0);
239  for(int y = 0; y < height; y += 1)
240  {
241  const int iy = height - (y + 1);
242 
243  ASSERTX(is_within_inclusive_as_int(0, iy, height - 1), iy, y, height);
244  for(int x = 0; x < width; x += 1)
245  {
246  const int target_index = (x + width * y) * number_of_components;
247  const int source_index = (x + width * iy) * number_of_components;
248  for(int component = 0; component < number_of_components; component += 1)
249  {
250  pixels[target_index + component] = components[source_index + component];
251  }
252  }
253  }
254 
255  int size = 0;
256  int size_result = write_image_data
257  (
258  determine_image_size,
259  &size,
260  width,
261  height,
262  number_of_components,
263  pixels.data(),
264  format,
265  jpeg_quality
266  );
267  if(size_result == 0)
268  {
269  return MemoryChunk::create_null();
270  }
271 
272  ASSERT(size > 0);
274  int write_result = write_image_data
275  (
276  write_memorychunk_to_file,
277  &file,
278  width,
279  height,
280  number_of_components,
281  pixels.data(),
282  format,
283  jpeg_quality
284  );
285  if(write_result == 0)
286  {
287  return MemoryChunk::create_null();
288  }
289 
290  return file.data;
291  }
292 
293 
296  {
297  auto file_memory = fs->read_file(path);
298  if(file_memory == nullptr)
299  {
300  ImageLoadResult result;
301  result.error = "File doesnt exist";
302  result.image.make_invalid();
303  LOG_ERROR("Failed to open {0} File doesnt exist.", path);
304  return result;
305  }
306 
307  return load_image(file_memory, path.path, alpha);
308  }
309 
310  ImageLoadResult
312  (
313  std::shared_ptr<MemoryChunk> file_memory,
314  const std::string& path,
315  AlphaLoad alpha
316  )
317  {
318  int channels = 0;
319  int image_width = 0;
320  int image_height = 0;
321 
322  // signed to unsigned cast is probably ok since both are considered to be a chunk of memory
323  // https://stackoverflow.com/questions/310451/should-i-use-static-cast-or-reinterpret-cast-when-casting-a-void-to-whatever
324  unsigned char* data = stbi_load_from_memory
325  (
326  reinterpret_cast<unsigned char*>(file_memory->get_data()), // NOLINT
327  file_memory->get_size(),
328  &image_width,
329  &image_height,
330  &channels,
331  0
332  );
333 
334  if(data == nullptr)
335  {
336  ImageLoadResult result;
337  result.error = stbi_failure_reason();
338  result.image.make_invalid();
339  LOG_ERROR("Failed to read {0}: {1}", path, result.error);
340  return result;
341  }
342 
343  bool has_alpha = false;
344  if(alpha == AlphaLoad::keep)
345  {
346  has_alpha = channels == 2 || channels == 4;
347  }
348 
349  ImageLoadResult result;
350  if(has_alpha)
351  {
352  result.image.setup_with_alpha_support(image_width, image_height, -1);
353  }
354  else
355  {
356  result.image.setup_no_alpha_support(image_width, image_height, -1);
357  }
358 
359  for(int y = 0; y < image_height; ++y)
360  {
361  for(int x = 0; x < image_width; ++x)
362  {
363  const int src_index = (y * image_width + x) * channels;
364 
365  // get component values
366  const unsigned char zero = 0;
367  const unsigned char c1 = data[src_index + 0]; // NOLINT no garbage value
368  const unsigned char c2 = (channels <= 1) ? zero : data[src_index + 1];
369  const unsigned char c3 = (channels <= 2) ? zero : data[src_index + 2];
370  const unsigned char c4 = (channels <= 3) ? zero : data[src_index + 3];
371 
372  auto get_color_from_channel = []
373  (
374  int channel,
375  unsigned char a,
376  unsigned char b,
377  unsigned char c,
378  unsigned char d
379  ) -> unsigned char
380  {
381  switch(channel)
382  {
383  case 1: return a; // grey
384  case 2: return b; // grey, alpha
385  case 3: return c; // red, green, blue
386  case 4: return d; // red, green, blue, alpha
387  default:
388  DIE("unhandled Select channel");
389  return 0;
390  }
391  };
392 
393  // Gray, Gray+alpha, RGB, RGB+alpha: gr gra rgb rgba
394  const unsigned char r = c1; // c1 c1 c1 c1
395  const unsigned char g = get_color_from_channel(channels, c1, c1, c2, c2);
396  const unsigned char b = get_color_from_channel(channels, c1, c1, c3, c3);
397  const unsigned char a = get_color_from_channel(channels, 255, c2, 255, c4);
398 
399  result.image.set_pixel(x, image_height - (y + 1), r, g, b, a);
400  }
401  }
402 
403  stbi_image_free(data);
404  return result;
405  }
406 
407  ImageLoadResult
409  (
410  void* compressed_data,
411  int compressed_size,
412  const std::string& path,
413  AlphaLoad alpha
414  )
415  {
416  auto dec = Decompressor{};
417  const unsigned int decompressed_size = Decompressor::stb_decompress_length(static_cast<const unsigned char*>(compressed_data));
418  auto decompressed = MemoryChunk::allocate(c_unsigned_int_to_int(decompressed_size));
419  const auto len = dec.stb_decompress
420  (
421  reinterpret_cast<unsigned char*>(decompressed->get_data()), // NOLINT
422  reinterpret_cast<const unsigned char*>(compressed_data), // NOLINT
423  static_cast<unsigned int>(compressed_size)
424  );
425  if(len == 0)
426  {
427  ImageLoadResult result;
428  result.error = "failed to decompress before loading image";
429  result.image.make_invalid();
430  return result;
431  }
432 
433  return load_image(decompressed, path, alpha);
434  }
435 }
436 
#define ASSERTX(x,...)
Definition: assert.h:48
#define ASSERT(x)
Definition: assert.h:29
#define DIE(message)
Definition: assert.h:67
#define LOG_ERROR(...)
Definition: log.h:9
ImageWriteFormat
Definition: image.h:20
int write_image_data(stbi_write_func *func, void *context, int w, int h, int comp, const void *data, ImageWriteFormat format, int jpeg_quality)
Definition: image.cc:204
AlphaLoad
Definition: image.h:139
ImageLoadResult load_image(io::FileSystem *fs, const io::FilePath &path, AlphaLoad alpha)
Definition: image.cc:295
size_t c_int_to_sizet(int i)
Definition: cint.cc:35
bool is_within_inclusive_as_int(int min, int c, int max)
Definition: numeric.cc:116
int c_unsigned_int_to_int(unsigned int i)
Definition: cint.cc:19
void write(const void *src, int size)
Definition: memorychunk.cc:83
static std::shared_ptr< MemoryChunk > allocate(int size)
Definition: memorychunk.cc:40
static std::shared_ptr< MemoryChunk > create_null()
Definition: memorychunk.cc:47
static Recti from_width_height(int width, int height)
Definition: rect.cc:573
Definition: rgb.h:45
Definition: rgb.h:26
static unsigned int stb_decompress_length(const unsigned char *input)
Definition: decompress.cc:11
core::Image image
Definition: image.h:133
std::string error
Definition: image.h:134
bool has_alpha
Definition: image.h:34
Recti get_indices() const
Definition: image.cc:168
std::vector< unsigned char > components
Definition: image.h:31
void set_pixel(int x, int y, const Rgbai &color)
Definition: image.cc:104
int get_pixel_byte_size() const
Definition: image.cc:32
void setup_with_alpha_support(int image_width, int image_height, int default_value=0)
if default value is negative, default value is ignored, otherwise its the default value for both R,...
Definition: image.cc:40
bool is_valid() const
Definition: image.cc:161
int get_pixel_index(int x, int y) const
Definition: image.cc:94
Rgbai get_pixel(int x, int y) const
Definition: image.cc:137
void setup_no_alpha_support(int image_width, int image_height, int default_value=0)
Definition: image.cc:52
const unsigned char * get_pixel_data() const
Definition: image.cc:178
void setup(int image_width, int image_height, bool alpha, int default_value)
Definition: image.cc:63
void make_invalid()
Definition: image.cc:20
std::shared_ptr< MemoryChunk > write(ImageWriteFormat format, int jpeg_quality=100) const
Definition: image.cc:233
int height
Definition: image.h:33
std::string path
contains either .
Definition: vfs_path.h:39
std::shared_ptr< MemoryChunk > read_file(const FilePath &path)
Definition: vfs.cc:86