satdump/src-core/common/image/image.cpp
2024-05-11 14:46:56 -04:00

562 lines
No EOL
17 KiB
C++

#include "image.h"
#include <cstdlib>
#include "core/exception.h"
#include "meta.h"
#include <cmath>
namespace image
{
Image::Image()
{
// Do nothing
}
Image::Image(int bit_depth, size_t width, size_t height, int channels)
{
init(bit_depth, width, height, channels);
}
Image::Image(const Image &img)
{
// Copy contents of the image over
init(img.d_depth, img.d_width, img.d_height, img.d_channels);
memcpy(d_data, img.d_data, img.data_size * img.type_size);
copy_meta(img);
}
Image &Image::operator=(const Image &img)
{
// Copy contents of the image over
init(img.d_depth, img.d_width, img.d_height, img.d_channels);
memcpy(d_data, img.d_data, img.data_size * img.type_size);
copy_meta(img);
return *this;
}
Image::Image(void *buffer, int bit_depth, size_t width, size_t height, int channels)
{
// Copy contents of the image over
init(bit_depth, width, height, channels);
memcpy(d_data, buffer, data_size * type_size);
}
Image::~Image()
{
if (d_data != nullptr)
{
free(d_data);
d_data = nullptr;
}
free_metadata(*this);
}
void Image::init(int bit_depth, size_t width, size_t height, int channels)
{
// Reset image if we already had one
if (d_data != nullptr)
{
free(d_data);
d_data = nullptr;
}
// Internal params
if (bit_depth > 8)
type_size = 2;
else
type_size = 1;
// Init buffer
data_size = width * height * channels;
d_data = malloc(type_size * data_size);
if (d_data == nullptr)
throw satdump_exception("Could not allocate memory for image!");
// Set to 0
memset(d_data, 0, type_size * data_size);
// Init local variables
d_depth = bit_depth;
d_maxv = (2 << (d_depth - 1)) - 1;
d_width = width;
d_height = height;
d_channels = channels;
}
void Image::clear()
{
// Reset image
if (d_data != nullptr)
free(d_data);
d_data = nullptr;
}
int Image::clamp(int input)
{
if (input > d_maxv)
return d_maxv;
else if (input < 0)
return 0;
else
return input;
}
double Image::clampf(double input)
{
if (input > 1.0)
return 1.0;
else if (input < 0)
return 0;
else
return input;
}
void Image::to_rgb()
{
if (d_channels == 1)
{
Image tmp = *this; // Backup image
init(d_depth, d_width, d_height, 3); // Init new image as RGB
// Fill in all 3 channels
draw_image(0, tmp);
draw_image(1, tmp);
draw_image(2, tmp);
}
else if (d_channels == 4)
{
Image tmp = *this; // Backup image
init(d_depth, d_width, d_height, 3); // Init new image as RGB
// Copy over all 3 channels
memcpy(d_data, tmp.d_data, d_width * d_height * 3 * type_size);
}
}
void Image::to_rgba()
{
if (d_channels == 1)
{
Image tmp = *this; // Backup image
init(d_depth, d_width, d_height, 4); // Init new image as RGBA
// Copy over all 3 channels
memcpy((uint8_t *)d_data + type_size * d_width * d_height * 0, tmp.d_data, d_width * d_height * type_size);
memcpy((uint8_t *)d_data + type_size * d_width * d_height * 1, tmp.d_data, d_width * d_height * type_size);
memcpy((uint8_t *)d_data + type_size * d_width * d_height * 2, tmp.d_data, d_width * d_height * type_size);
for (size_t i = 0; i < d_width * d_height; i++)
set(3, i, d_maxv);
}
else if (d_channels == 2)
{
Image tmp = *this; // Backup image
init(d_depth, d_width, d_height, 4); // Init new image as RGBA
// Copy over all 3 channels
memcpy((uint8_t *)d_data + type_size * d_width * d_height * 0, tmp.d_data, d_width * d_height * type_size);
memcpy((uint8_t *)d_data + type_size * d_width * d_height * 1, tmp.d_data, d_width * d_height * type_size);
memcpy((uint8_t *)d_data + type_size * d_width * d_height * 2, tmp.d_data, d_width * d_height * type_size);
// Copy over RGBA
memcpy((uint8_t *)d_data + type_size * d_width * d_height * 3, (uint8_t *)tmp.d_data + d_width * d_height, d_width * d_height * type_size);
}
else if (d_channels == 3)
{
Image tmp = *this; // Backup image
init(d_depth, d_width, d_height, 4); // Init new image as RGBA
// Copy over all 3 channels
memcpy(d_data, tmp.d_data, d_width * d_height * 3 * type_size);
// Fill in RGBA
for (size_t i = 0; i < d_width * d_height; i++)
set(3, i, d_maxv);
}
}
Image Image::to8bits()
{
if (d_depth == 8)
{
return *this;
}
else if (d_depth == 16)
{
Image image8(8, d_width, d_height, d_channels);
for (size_t i = 0; i < data_size; i++)
image8.set(i, get(i) >> 8);
return image8;
}
throw satdump_exception("Error in to8bits()"); // This should never happen
}
Image Image::to16bits()
{
if (d_depth == 16)
{
return *this;
}
else if (d_depth == 8)
{
Image image16(16, d_width, d_height, d_channels);
for (size_t i = 0; i < data_size; i++)
image16.set(i, get(i) << 8);
return image16;
}
throw satdump_exception("Error in to8bits()"); // This should never happen
}
Image Image::to_depth(int bit_depth)
{
if (bit_depth > 8)
return to16bits();
else
return to8bits();
}
void Image::crop(int x0, int y0, int x1, int y1)
{
int new_width = x1 - x0;
int new_height = y1 - y0;
// Create new buffer
void *new_data = malloc(new_width * new_height * d_channels * type_size);
// Copy cropped area to new region
for (int c = 0; c < d_channels; c++)
for (int x = 0; x < new_width; x++)
for (int y = 0; y < new_height; y++)
memcpy((uint8_t *)new_data + ((new_width * new_height * c) + y * new_width + x) * type_size,
(uint8_t *)d_data + (c * d_width * d_height + (y0 + y) * d_width + (x + x0)) * type_size,
type_size);
// Swap out buffer
free(d_data);
d_data = new_data;
// Update info
data_size = new_width * new_height * d_channels;
d_width = new_width;
d_height = new_height;
}
void Image::crop(int x0, int x1)
{
crop(x0, 0, x1, d_height);
}
Image Image::crop_to(int x0, int y0, int x1, int y1)
{
int new_width = x1 - x0;
int new_height = y1 - y0;
// Create new buffer
Image new_data(d_depth, new_width, new_height, d_channels);
// Copy cropped area to new region
for (int c = 0; c < d_channels; c++)
for (int x = 0; x < new_width; x++)
for (int y = 0; y < new_height; y++)
new_data.set(c, x, y, get(c, (x + x0), (y0 + y)));
return new_data;
}
Image Image::crop_to(int x0, int x1)
{
return crop_to(x0, 0, x1, d_height);
}
void Image::mirror(bool x, bool y)
{
if (y) // Mirror on the Y axis
{
int *tmp_col = (int *)malloc(d_height * sizeof(int));
for (int c = 0; c < d_channels; c++)
{
for (size_t col = 0; col < d_width; col++)
{
for (size_t i = 0; i < d_height; i++) // Buffer column
tmp_col[i] = get(c, col, i);
for (size_t i = 0; i < d_height; i++) // Restore and mirror
set(c, col, i, tmp_col[(d_height - 1) - i]);
}
}
free(tmp_col);
}
if (x) // Mirror on the X axis
{
int *tmp_row = (int *)malloc(d_width * sizeof(int));
for (int c = 0; c < d_channels; c++)
{
for (size_t row = 0; row < d_height; row++)
{
for (size_t i = 0; i < d_width; i++) // Buffer column
tmp_row[i] = get(c, i, row);
for (size_t i = 0; i < d_width; i++) // Restore and mirror
set(c, i, row, tmp_row[(d_width - 1) - i]);
}
}
free(tmp_row);
}
}
void Image::resize(int width, int height)
{
double x_scale = double(d_width) / double(width);
double y_scale = double(d_height) / double(height);
Image tmp = *this;
init(d_depth, width, height, d_channels);
for (int c = 0; c < d_channels; c++)
{
for (size_t x = 0; x < d_width; x++)
{
for (size_t y = 0; y < d_height; y++)
{
int xx = floor(double(x) * x_scale);
int yy = floor(double(y) * y_scale);
set(c, x, y, tmp.get(c, xx, yy));
}
}
}
}
Image Image::resize_to(int width, int height)
{
double x_scale = double(d_width) / double(width);
double y_scale = double(d_height) / double(height);
Image ret(d_depth, width, height, d_channels);
for (int c = 0; c < d_channels; c++)
{
for (size_t x = 0; x < (size_t)width; x++)
{
for (size_t y = 0; y < (size_t)height; y++)
{
int xx = floor(double(x) * x_scale);
int yy = floor(double(y) * y_scale);
ret.set(c, x, y, get(c, xx, yy));
}
}
}
return ret;
}
void Image::resize_bilinear(int width, int height, bool text_mode)
{
int a = 0, b = 0, c = 0, d = 0, x = 0, y = 0;
size_t index;
double x_scale = double(d_width - 1) / double(width);
double y_scale = double(d_height - 1) / double(height);
float x_diff, y_diff, val;
Image tmp = *this;
init(d_depth, width, height, d_channels);
size_t max_index = tmp.width() * tmp.height();
for (int cc = 0; cc < d_channels; cc++)
{
for (int i = 0; i < height; i++)
{
for (int j = 0; j < width; j++)
{
x = (int)(x_scale * j);
y = (int)(y_scale * i);
x_diff = (x_scale * j) - x;
y_diff = (y_scale * i) - y;
index = (y * tmp.width() + x);
a = tmp.get(cc, index);
if (index + 1 < max_index)
b = tmp.get(cc, index + 1);
if (index + tmp.width() < max_index)
c = tmp.get(cc, index + tmp.width());
if (index + tmp.width() + 1 < max_index)
d = tmp.get(cc, index + tmp.width() + 1);
val = a * (1 - x_diff) * (1 - y_diff) +
b * (x_diff) * (1 - y_diff) +
c * (y_diff) * (1 - x_diff) +
d * (x_diff * y_diff);
if (text_mode) // Special text mode, where we want to keep it clear whatever the res is
set(cc, i * width + j, val > 0 ? d_maxv : 0);
else
set(cc, i * width + j, val);
}
}
}
}
int Image::get_pixel_bilinear(int cc, double rx, double ry)
{
size_t x = (size_t)rx;
size_t y = (size_t)ry;
double x_diff = rx - x;
double y_diff = ry - y;
size_t index = (y * d_width + x);
size_t max_index = d_width * d_height;
int a = 0, b = 0, c = 0, d = 0;
float a_a = 1.0f, b_a = 1.0f, c_a = 1.0f, d_a = 1.0f;
a = get(cc, index);
if (d_channels == 4 && cc != 3)
a_a = (float)get(3, index) / (float)d_maxv;
if (index + 1 < max_index)
{
b = get(cc, index + 1);
if (d_channels == 4 && cc != 3)
{
b_a = (float)get(3, index + 1) / (float)d_maxv;
b = (float)b * b_a;
}
}
else
return a;
if (index + d_width < max_index)
{
c = get(cc, index + d_width);
if (d_channels == 4 && cc != 3)
{
c_a = (float)get(3, index + d_width) / (float)d_maxv;
c = (float)c * c_a;
}
}
else
return a;
if (index + d_width + 1 < max_index)
{
d = get(cc, index + d_width + 1);
if (d_channels == 4 && cc != 3)
{
d_a = (float)get(3, index + d_width + 1) / (float)d_maxv;
d = (float)d * d_a;
}
}
else
return a;
if (x == d_width - 1)
return a;
if (y == d_height - 1)
return a;
a = (float)a * a_a;
int ret = clamp(a * (1 - x_diff) * (1 - y_diff) +
b * (x_diff) * (1 - y_diff) +
c * (y_diff) * (1 - x_diff) +
d * (x_diff * y_diff));
if (d_channels == 4 && cc != 3)
{
ret = (float)ret / (a_a * (1 - x_diff) * (1 - y_diff) +
b_a * (x_diff) * (1 - y_diff) +
c_a * (y_diff) * (1 - x_diff) +
d_a * (x_diff * y_diff));
}
return ret;
}
void Image::fill(int val)
{
for (int c = 0; c < d_channels; c++)
for (size_t i = 0; i < d_width * d_height; i++)
set(c, i, val);
}
void Image::fill_color(std::vector<double> color)
{
for (size_t x = 0; x < d_width; x++)
for (size_t y = 0; y < d_height; y++)
draw_pixel(x, y, color);
}
////////////////////////
void imemcpy(Image &img1, size_t pos1, Image &img2, size_t pos2, size_t px_size)
{
if (img1.depth() != img2.depth())
throw satdump_exception("image::memcpy both images must be the same bit depth!");
if (pos1 + px_size > img1.size())
throw satdump_exception("image::memcpy pos1 + px_size exceeds img1 size!");
if (pos2 + px_size > img2.size())
throw satdump_exception("image::memcpy pos2 + px_size exceeds img2 size!");
memcpy((uint8_t *)img1.raw_data() + pos1 * img1.typesize(),
(uint8_t *)img2.raw_data() + pos2 * img1.typesize(),
px_size * img1.typesize());
}
void image_to_rgba(Image &img, uint32_t *output)
{
int shift = img.depth() - 8;
if (img.channels() == 1)
{
for (size_t i = 0; i < img.width() * img.height(); i++)
{
uint8_t c;
c = img.get(i) >> shift;
output[i] = ((uint32_t)255 << 24) | ((uint32_t)c << 16) | ((uint32_t)c << 8) | (uint32_t)c;
}
}
else if (img.channels() == 2)
{
for (size_t i = 0; i < img.width() * img.height(); i++)
{
uint8_t r, g, b, a;
r = img.get(0, i) >> shift;
g = img.get(0, i) >> shift;
b = img.get(0, i) >> shift;
a = img.get(1, i) >> shift;
output[i] = ((uint32_t)a << 24) | ((uint32_t)b << 16) | ((uint32_t)g << 8) | (uint32_t)r;
}
}
else if (img.channels() == 3)
{
for (size_t i = 0; i < img.width() * img.height(); i++)
{
uint8_t r, g, b;
r = img.get(0, i) >> shift;
g = img.get(1, i) >> shift;
b = img.get(2, i) >> shift;
output[i] = ((uint32_t)255 << 24) | ((uint32_t)b << 16) | ((uint32_t)g << 8) | (uint32_t)r;
}
}
else if (img.channels() == 4)
{
for (size_t i = 0; i < img.width() * img.height(); i++)
{
uint8_t r, g, b, a;
r = img.get(0, i) >> shift;
g = img.get(1, i) >> shift;
b = img.get(2, i) >> shift;
a = img.get(3, i) >> shift;
output[i] = ((uint32_t)a << 24) | ((uint32_t)b << 16) | ((uint32_t)g << 8) | (uint32_t)r;
}
}
}
}