270 lines
7.7 KiB
C++
270 lines
7.7 KiB
C++
#ifndef DWM_PLUGINS_MEDIA_UTILS_H
|
|
#define DWM_PLUGINS_MEDIA_UTILS_H
|
|
|
|
#include <algorithm>
|
|
#include <cctype>
|
|
#include <csetjmp>
|
|
#include <cstdint>
|
|
#include <cstdio>
|
|
#include <cstdlib>
|
|
#include <cstring>
|
|
#include <fstream>
|
|
#include <string>
|
|
#include <vector>
|
|
|
|
#include <jpeglib.h>
|
|
#include <png.h>
|
|
|
|
namespace bananarchy {
|
|
|
|
struct JpegErrMgr {
|
|
struct jpeg_error_mgr pub;
|
|
jmp_buf setjmpBuf;
|
|
};
|
|
|
|
inline void jpegErrorExit(j_common_ptr cinfo) {
|
|
JpegErrMgr* myerr = reinterpret_cast<JpegErrMgr*>(cinfo->err);
|
|
longjmp(myerr->setjmpBuf, 1);
|
|
}
|
|
|
|
inline bool saveRgb24AsJpeg(const std::string& path,
|
|
const uint8_t* rgb,
|
|
int width,
|
|
int height,
|
|
int quality = 92) {
|
|
if (!rgb || width <= 0 || height <= 0) {
|
|
return false;
|
|
}
|
|
FILE* fp = std::fopen(path.c_str(), "wb");
|
|
if (!fp) {
|
|
return false;
|
|
}
|
|
|
|
struct jpeg_compress_struct cinfo {};
|
|
JpegErrMgr jerr {};
|
|
cinfo.err = jpeg_std_error(&jerr.pub);
|
|
jerr.pub.error_exit = jpegErrorExit;
|
|
if (setjmp(jerr.setjmpBuf)) {
|
|
jpeg_destroy_compress(&cinfo);
|
|
std::fclose(fp);
|
|
return false;
|
|
}
|
|
|
|
jpeg_create_compress(&cinfo);
|
|
jpeg_stdio_dest(&cinfo, fp);
|
|
cinfo.image_width = static_cast<JDIMENSION>(width);
|
|
cinfo.image_height = static_cast<JDIMENSION>(height);
|
|
cinfo.input_components = 3;
|
|
cinfo.in_color_space = JCS_RGB;
|
|
|
|
jpeg_set_defaults(&cinfo);
|
|
jpeg_set_quality(&cinfo, quality, TRUE);
|
|
jpeg_start_compress(&cinfo, TRUE);
|
|
|
|
int rowStride = width * 3;
|
|
while (cinfo.next_scanline < cinfo.image_height) {
|
|
JSAMPROW rowPtr =
|
|
const_cast<JSAMPROW>(rgb + cinfo.next_scanline * rowStride);
|
|
jpeg_write_scanlines(&cinfo, &rowPtr, 1);
|
|
}
|
|
|
|
jpeg_finish_compress(&cinfo);
|
|
jpeg_destroy_compress(&cinfo);
|
|
std::fclose(fp);
|
|
return true;
|
|
}
|
|
|
|
inline bool loadJpegRgb24(const std::string& path,
|
|
std::vector<uint8_t>* outRgb,
|
|
int* outW,
|
|
int* outH) {
|
|
FILE* fp = std::fopen(path.c_str(), "rb");
|
|
if (!fp) {
|
|
return false;
|
|
}
|
|
|
|
struct jpeg_decompress_struct cinfo {};
|
|
JpegErrMgr jerr {};
|
|
cinfo.err = jpeg_std_error(&jerr.pub);
|
|
jerr.pub.error_exit = jpegErrorExit;
|
|
if (setjmp(jerr.setjmpBuf)) {
|
|
jpeg_destroy_decompress(&cinfo);
|
|
std::fclose(fp);
|
|
return false;
|
|
}
|
|
|
|
jpeg_create_decompress(&cinfo);
|
|
jpeg_stdio_src(&cinfo, fp);
|
|
if (jpeg_read_header(&cinfo, TRUE) != JPEG_HEADER_OK) {
|
|
jpeg_destroy_decompress(&cinfo);
|
|
std::fclose(fp);
|
|
return false;
|
|
}
|
|
|
|
// Use libjpeg fast DCT downscaling for large images so decoding is instant
|
|
if (cinfo.image_width > 960 || cinfo.image_height > 960) {
|
|
cinfo.scale_num = 1;
|
|
cinfo.scale_denom = 2;
|
|
}
|
|
cinfo.out_color_space = JCS_RGB;
|
|
jpeg_start_decompress(&cinfo);
|
|
|
|
int w = static_cast<int>(cinfo.output_width);
|
|
int h = static_cast<int>(cinfo.output_height);
|
|
if (w <= 0 || h <= 0 || w > 8192 || h > 8192) {
|
|
jpeg_destroy_decompress(&cinfo);
|
|
std::fclose(fp);
|
|
return false;
|
|
}
|
|
|
|
outRgb->resize(static_cast<size_t>(w) * h * 3);
|
|
int rowStride = w * 3;
|
|
while (cinfo.output_scanline < cinfo.output_height) {
|
|
JSAMPROW rowPtr = outRgb->data() + cinfo.output_scanline * rowStride;
|
|
jpeg_read_scanlines(&cinfo, &rowPtr, 1);
|
|
}
|
|
|
|
jpeg_finish_decompress(&cinfo);
|
|
jpeg_destroy_decompress(&cinfo);
|
|
std::fclose(fp);
|
|
*outW = w;
|
|
*outH = h;
|
|
return true;
|
|
}
|
|
|
|
inline bool loadPngRgb24(const std::string& path,
|
|
std::vector<uint8_t>* outRgb,
|
|
int* outW,
|
|
int* outH) {
|
|
png_image image {};
|
|
image.version = PNG_IMAGE_VERSION;
|
|
if (!png_image_begin_read_from_file(&image, path.c_str())) {
|
|
return false;
|
|
}
|
|
image.format = PNG_FORMAT_RGB;
|
|
int w = static_cast<int>(image.width);
|
|
int h = static_cast<int>(image.height);
|
|
if (w <= 0 || h <= 0 || w > 8192 || h > 8192) {
|
|
png_image_free(&image);
|
|
return false;
|
|
}
|
|
outRgb->resize(PNG_IMAGE_SIZE(image));
|
|
if (!png_image_finish_read(&image, nullptr, outRgb->data(), 0, nullptr)) {
|
|
png_image_free(&image);
|
|
return false;
|
|
}
|
|
png_image_free(&image);
|
|
*outW = w;
|
|
*outH = h;
|
|
return true;
|
|
}
|
|
|
|
inline bool loadImageRgb24(const std::string& path,
|
|
std::vector<uint8_t>* outRgb,
|
|
int* outW,
|
|
int* outH) {
|
|
std::string low = path;
|
|
for (char& c : low) {
|
|
c = static_cast<char>(std::tolower(static_cast<unsigned char>(c)));
|
|
}
|
|
if (low.size() >= 4 && low.substr(low.size() - 4) == ".png") {
|
|
if (loadPngRgb24(path, outRgb, outW, outH)) {
|
|
return true;
|
|
}
|
|
}
|
|
if (loadJpegRgb24(path, outRgb, outW, outH)) {
|
|
return true;
|
|
}
|
|
if (loadPngRgb24(path, outRgb, outW, outH)) {
|
|
return true;
|
|
}
|
|
return false;
|
|
}
|
|
|
|
inline void scaleRgb24ToBgrxCanvas(const uint8_t* srcRgb,
|
|
int srcW,
|
|
int srcH,
|
|
uint32_t* dstBgrx,
|
|
int canvasW,
|
|
int canvasH,
|
|
uint32_t bgPixel = 0x111111) {
|
|
for (int i = 0; i < canvasW * canvasH; ++i) {
|
|
dstBgrx[i] = bgPixel;
|
|
}
|
|
if (!srcRgb || srcW <= 0 || srcH <= 0 || canvasW <= 0 || canvasH <= 0) {
|
|
return;
|
|
}
|
|
|
|
int drawW = canvasW;
|
|
int drawH = (srcH * canvasW) / srcW;
|
|
if (drawH > canvasH) {
|
|
drawH = canvasH;
|
|
drawW = (srcW * canvasH) / srcH;
|
|
}
|
|
drawW = std::max(1, drawW);
|
|
drawH = std::max(1, drawH);
|
|
|
|
int offX = (canvasW - drawW) / 2;
|
|
int offY = (canvasH - drawH) / 2;
|
|
|
|
for (int dy = 0; dy < drawH; ++dy) {
|
|
int sy = std::min(srcH - 1, (dy * srcH) / drawH);
|
|
const uint8_t* srow = srcRgb + static_cast<size_t>(sy) * srcW * 3;
|
|
uint32_t* drow = dstBgrx + static_cast<size_t>(offY + dy) * canvasW + offX;
|
|
for (int dx = 0; dx < drawW; ++dx) {
|
|
int sx = std::min(srcW - 1, (dx * srcW) / drawW);
|
|
const uint8_t* px = srow + sx * 3;
|
|
drow[dx] = (static_cast<uint32_t>(px[0]) << 16) |
|
|
(static_cast<uint32_t>(px[1]) << 8) |
|
|
static_cast<uint32_t>(px[2]);
|
|
}
|
|
}
|
|
}
|
|
|
|
inline void downscaleViewfinder800x600To240x180(const uint8_t* srcRgb,
|
|
uint32_t* dstBgrx,
|
|
int zoomFactor = 1) {
|
|
if (!srcRgb || !dstBgrx) {
|
|
return;
|
|
}
|
|
zoomFactor = std::clamp(zoomFactor, 1, 4);
|
|
int cropW = 800 / zoomFactor;
|
|
int cropH = 600 / zoomFactor;
|
|
int startX = (800 - cropW) / 2;
|
|
int startY = (600 - cropH) / 2;
|
|
|
|
for (int dy = 0; dy < 180; ++dy) {
|
|
int sy = startY + (dy * cropH) / 180;
|
|
const uint8_t* srow = srcRgb + static_cast<size_t>(sy) * 800 * 3;
|
|
uint32_t* drow = dstBgrx + static_cast<size_t>(dy) * 240;
|
|
for (int dx = 0; dx < 240; ++dx) {
|
|
int sx = startX + (dx * cropW) / 240;
|
|
const uint8_t* px = srow + sx * 3;
|
|
drow[dx] = (static_cast<uint32_t>(px[0]) << 16) |
|
|
(static_cast<uint32_t>(px[1]) << 8) |
|
|
static_cast<uint32_t>(px[2]);
|
|
}
|
|
}
|
|
}
|
|
|
|
inline std::string formatFileSize(off_t bytes) {
|
|
char buf[32];
|
|
if (bytes >= 1024LL * 1024LL * 1024LL) {
|
|
std::snprintf(buf, sizeof(buf), "%.1fG",
|
|
static_cast<double>(bytes) / (1024.0 * 1024.0 * 1024.0));
|
|
} else if (bytes >= 1024LL * 1024LL) {
|
|
std::snprintf(buf, sizeof(buf), "%.1fM",
|
|
static_cast<double>(bytes) / (1024.0 * 1024.0));
|
|
} else if (bytes >= 1024LL) {
|
|
std::snprintf(buf, sizeof(buf), "%.1fK",
|
|
static_cast<double>(bytes) / 1024.0);
|
|
} else {
|
|
std::snprintf(buf, sizeof(buf), "%lldB", static_cast<long long>(bytes));
|
|
}
|
|
return buf;
|
|
}
|
|
|
|
} // namespace bananarchy
|
|
|
|
#endif // DWM_PLUGINS_MEDIA_UTILS_H
|