Refactor images related code
* move different formats support to its own directory * add common CaptureBufferToFile subroutinepull/943/head
parent
ad5f3d31b3
commit
77af6f7419
|
@ -1,7 +1,10 @@
|
||||||
set(CELESTIA_LIBS
|
set(CELESTIA_LIBS
|
||||||
cel3ds
|
cel3ds
|
||||||
|
celcompat
|
||||||
celengine
|
celengine
|
||||||
|
celephem
|
||||||
celestia
|
celestia
|
||||||
|
celimage
|
||||||
celmath
|
celmath
|
||||||
celmodel
|
celmodel
|
||||||
celttf
|
celttf
|
||||||
|
@ -14,10 +17,6 @@ endforeach()
|
||||||
|
|
||||||
add_subdirectory(celscript)
|
add_subdirectory(celscript)
|
||||||
|
|
||||||
# These compiled objects are merged with the celengine library
|
|
||||||
add_subdirectory(celephem)
|
|
||||||
add_subdirectory(celcompat)
|
|
||||||
|
|
||||||
if (ENABLE_TOOLS)
|
if (ENABLE_TOOLS)
|
||||||
add_subdirectory(tools)
|
add_subdirectory(tools)
|
||||||
endif()
|
endif()
|
||||||
|
|
|
@ -26,9 +26,6 @@ set(CELENGINE_SOURCES
|
||||||
constellation.h
|
constellation.h
|
||||||
curveplot.cpp
|
curveplot.cpp
|
||||||
curveplot.h
|
curveplot.h
|
||||||
dds.cpp
|
|
||||||
dds_decompress.cpp
|
|
||||||
dds_decompress.h
|
|
||||||
deepskyobj.cpp
|
deepskyobj.cpp
|
||||||
deepskyobj.h
|
deepskyobj.h
|
||||||
dispmap.cpp
|
dispmap.cpp
|
||||||
|
|
|
@ -588,8 +588,7 @@ GalacticForm* buildGalacticForms(const fs::path& filename)
|
||||||
int width, height, rgb, j = 0, kmin = 9;
|
int width, height, rgb, j = 0, kmin = 9;
|
||||||
unsigned char value;
|
unsigned char value;
|
||||||
float h = 0.75f;
|
float h = 0.75f;
|
||||||
Image* img;
|
Image* img = LoadImageFromFile(filename);
|
||||||
img = LoadPNGImage(filename);
|
|
||||||
if (img == nullptr)
|
if (img == nullptr)
|
||||||
{
|
{
|
||||||
cout<<"\nThe galaxy template *** "<<filename<<" *** could not be loaded!\n\n";
|
cout<<"\nThe galaxy template *** "<<filename<<" *** could not be loaded!\n\n";
|
||||||
|
|
|
@ -7,39 +7,28 @@
|
||||||
// as published by the Free Software Foundation; either version 2
|
// as published by the Free Software Foundation; either version 2
|
||||||
// of the License, or (at your option) any later version.
|
// of the License, or (at your option) any later version.
|
||||||
|
|
||||||
#include <config.h>
|
|
||||||
#include <algorithm>
|
#include <algorithm>
|
||||||
#include <cassert>
|
#include <cassert>
|
||||||
#include <cmath>
|
|
||||||
#include <cstdio>
|
|
||||||
#include <cstring>
|
|
||||||
#include <fstream>
|
#include <fstream>
|
||||||
#include <iostream>
|
#include <iostream>
|
||||||
|
#include <celengine/glsupport.h>
|
||||||
extern "C" {
|
|
||||||
#include <jpeglib.h>
|
|
||||||
}
|
|
||||||
#include <png.h>
|
|
||||||
|
|
||||||
#include "glsupport.h"
|
|
||||||
|
|
||||||
#include <celutil/debug.h>
|
#include <celutil/debug.h>
|
||||||
#include <celutil/filetype.h>
|
#include <celutil/filetype.h>
|
||||||
#include <celutil/gettext.h>
|
#include <celutil/gettext.h>
|
||||||
|
#include <celimage/imageformats.h>
|
||||||
#include "image.h"
|
#include "image.h"
|
||||||
|
|
||||||
|
|
||||||
using namespace std;
|
using namespace std;
|
||||||
|
|
||||||
|
namespace
|
||||||
|
{
|
||||||
// All rows are padded to a size that's a multiple of 4 bytes
|
// All rows are padded to a size that's a multiple of 4 bytes
|
||||||
static int pad(int n)
|
int pad(int n)
|
||||||
{
|
{
|
||||||
return (n + 3) & ~0x3;
|
return (n + 3) & ~0x3;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
int formatComponents(int fmt)
|
||||||
static int formatComponents(int fmt)
|
|
||||||
{
|
{
|
||||||
switch (fmt)
|
switch (fmt)
|
||||||
{
|
{
|
||||||
|
@ -73,8 +62,7 @@ static int formatComponents(int fmt)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
int calcMipLevelSize(int fmt, int w, int h, int mip)
|
||||||
static int calcMipLevelSize(int fmt, int w, int h, int mip)
|
|
||||||
{
|
{
|
||||||
w = max(w >> mip, 1);
|
w = max(w >> mip, 1);
|
||||||
h = max(h >> mip, 1);
|
h = max(h >> mip, 1);
|
||||||
|
@ -92,7 +80,7 @@ static int calcMipLevelSize(int fmt, int w, int h, int mip)
|
||||||
return h * pad(w * formatComponents(fmt));
|
return h * pad(w * formatComponents(fmt));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
} // anonymous namespace
|
||||||
|
|
||||||
Image::Image(int fmt, int w, int h, int mip) :
|
Image::Image(int fmt, int w, int h, int mip) :
|
||||||
width(w),
|
width(w),
|
||||||
|
@ -111,61 +99,51 @@ Image::Image(int fmt, int w, int h, int mip) :
|
||||||
pixels = new unsigned char[size];
|
pixels = new unsigned char[size];
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
Image::~Image()
|
Image::~Image()
|
||||||
{
|
{
|
||||||
delete[] pixels;
|
delete[] pixels;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
int Image::getWidth() const
|
int Image::getWidth() const
|
||||||
{
|
{
|
||||||
return width;
|
return width;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
int Image::getHeight() const
|
int Image::getHeight() const
|
||||||
{
|
{
|
||||||
return height;
|
return height;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
int Image::getPitch() const
|
int Image::getPitch() const
|
||||||
{
|
{
|
||||||
return pitch;
|
return pitch;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
int Image::getMipLevelCount() const
|
int Image::getMipLevelCount() const
|
||||||
{
|
{
|
||||||
return mipLevels;
|
return mipLevels;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
int Image::getSize() const
|
int Image::getSize() const
|
||||||
{
|
{
|
||||||
return size;
|
return size;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
int Image::getFormat() const
|
int Image::getFormat() const
|
||||||
{
|
{
|
||||||
return format;
|
return format;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
int Image::getComponents() const
|
int Image::getComponents() const
|
||||||
{
|
{
|
||||||
return components;
|
return components;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
unsigned char* Image::getPixels()
|
unsigned char* Image::getPixels()
|
||||||
{
|
{
|
||||||
return pixels;
|
return pixels;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
unsigned char* Image::getPixelRow(int mip, int row)
|
unsigned char* Image::getPixelRow(int mip, int row)
|
||||||
{
|
{
|
||||||
/*int w = max(width >> mip, 1); Unused*/
|
/*int w = max(width >> mip, 1); Unused*/
|
||||||
|
@ -180,13 +158,11 @@ unsigned char* Image::getPixelRow(int mip, int row)
|
||||||
return getMipLevel(mip) + row * pitch;
|
return getMipLevel(mip) + row * pitch;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
unsigned char* Image::getPixelRow(int row)
|
unsigned char* Image::getPixelRow(int row)
|
||||||
{
|
{
|
||||||
return getPixelRow(0, row);
|
return getPixelRow(0, row);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
unsigned char* Image::getMipLevel(int mip)
|
unsigned char* Image::getMipLevel(int mip)
|
||||||
{
|
{
|
||||||
if (mip >= mipLevels)
|
if (mip >= mipLevels)
|
||||||
|
@ -199,7 +175,6 @@ unsigned char* Image::getMipLevel(int mip)
|
||||||
return pixels + offset;
|
return pixels + offset;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
int Image::getMipLevelSize(int mip) const
|
int Image::getMipLevelSize(int mip) const
|
||||||
{
|
{
|
||||||
if (mip >= mipLevels)
|
if (mip >= mipLevels)
|
||||||
|
@ -208,7 +183,6 @@ int Image::getMipLevelSize(int mip) const
|
||||||
return calcMipLevelSize(format, width, height, mip);
|
return calcMipLevelSize(format, width, height, mip);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
bool Image::isCompressed() const
|
bool Image::isCompressed() const
|
||||||
{
|
{
|
||||||
switch (format)
|
switch (format)
|
||||||
|
@ -222,7 +196,6 @@ bool Image::isCompressed() const
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
bool Image::hasAlpha() const
|
bool Image::hasAlpha() const
|
||||||
{
|
{
|
||||||
switch (format)
|
switch (format)
|
||||||
|
@ -239,11 +212,12 @@ bool Image::hasAlpha() const
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
// Convert an input height map to a normal map. Ideally, a single channel
|
* Convert an input height map to a normal map. Ideally, a single channel
|
||||||
// input should be used. If not, the first color channel of the input image
|
* input should be used. If not, the first color channel of the input image
|
||||||
// is the one only one used when generating normals. This produces the
|
* is the one only one used when generating normals. This produces the
|
||||||
// expected results for grayscale values in RGB images.
|
* expected results for grayscale values in RGB images.
|
||||||
|
*/
|
||||||
Image* Image::computeNormalMap(float scale, bool wrap) const
|
Image* Image::computeNormalMap(float scale, bool wrap) const
|
||||||
{
|
{
|
||||||
// Can't do anything with compressed input; there are probably some other
|
// Can't do anything with compressed input; there are probably some other
|
||||||
|
@ -311,7 +285,6 @@ Image* Image::computeNormalMap(float scale, bool wrap) const
|
||||||
return normalMap;
|
return normalMap;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
Image* LoadImageFromFile(const fs::path& filename)
|
Image* LoadImageFromFile(const fs::path& filename)
|
||||||
{
|
{
|
||||||
ContentType type = DetermineFileType(filename);
|
ContentType type = DetermineFileType(filename);
|
||||||
|
@ -341,465 +314,3 @@ Image* LoadImageFromFile(const fs::path& filename)
|
||||||
|
|
||||||
return img;
|
return img;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
struct my_error_mgr
|
|
||||||
{
|
|
||||||
struct jpeg_error_mgr pub; // "public" fields
|
|
||||||
jmp_buf setjmp_buffer; // for return to caller
|
|
||||||
};
|
|
||||||
|
|
||||||
using my_error_ptr = struct my_error_mgr *;
|
|
||||||
|
|
||||||
METHODDEF(void) my_error_exit(j_common_ptr cinfo)
|
|
||||||
{
|
|
||||||
// cinfo->err really points to a my_error_mgr struct, so coerce pointer
|
|
||||||
auto myerr = (my_error_ptr) cinfo->err;
|
|
||||||
|
|
||||||
// Always display the message.
|
|
||||||
// We could postpone this until after returning, if we chose.
|
|
||||||
(*cinfo->err->output_message) (cinfo);
|
|
||||||
|
|
||||||
// Return control to the setjmp point
|
|
||||||
longjmp(myerr->setjmp_buffer, 1);
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
Image* LoadJPEGImage(const fs::path& filename, int /*unused*/)
|
|
||||||
{
|
|
||||||
Image* img = nullptr;
|
|
||||||
|
|
||||||
// This struct contains the JPEG decompression parameters and pointers to
|
|
||||||
// working space (which is allocated as needed by the JPEG library).
|
|
||||||
struct jpeg_decompress_struct cinfo;
|
|
||||||
|
|
||||||
// We use our private extension JPEG error handler.
|
|
||||||
// Note that this struct must live as long as the main JPEG parameter
|
|
||||||
// struct, to avoid dangling-pointer problems.
|
|
||||||
struct my_error_mgr jerr;
|
|
||||||
// More stuff
|
|
||||||
JSAMPARRAY buffer; // Output row buffer
|
|
||||||
int row_stride; // physical row width in output buffer
|
|
||||||
long cont;
|
|
||||||
|
|
||||||
// In this example we want to open the input file before doing anything else,
|
|
||||||
// so that the setjmp() error recovery below can assume the file is open.
|
|
||||||
// VERY IMPORTANT: use "b" option to fopen() if you are on a machine that
|
|
||||||
// requires it in order to read binary files.
|
|
||||||
|
|
||||||
FILE *in;
|
|
||||||
#ifdef _WIN32
|
|
||||||
in = _wfopen(filename.c_str(), L"rb");
|
|
||||||
#else
|
|
||||||
in = fopen(filename.c_str(), "rb");
|
|
||||||
#endif
|
|
||||||
if (!in)
|
|
||||||
return nullptr;
|
|
||||||
|
|
||||||
// Step 1: allocate and initialize JPEG decompression object
|
|
||||||
// We set up the normal JPEG error routines, then override error_exit.
|
|
||||||
cinfo.err = jpeg_std_error(&jerr.pub);
|
|
||||||
jerr.pub.error_exit = my_error_exit;
|
|
||||||
// Establish the setjmp return context for my_error_exit to use.
|
|
||||||
if (setjmp(jerr.setjmp_buffer))
|
|
||||||
{
|
|
||||||
// If we get here, the JPEG code has signaled an error.
|
|
||||||
// We need to clean up the JPEG object, close the input file, and return.
|
|
||||||
jpeg_destroy_decompress(&cinfo);
|
|
||||||
fclose(in);
|
|
||||||
delete img;
|
|
||||||
|
|
||||||
return nullptr;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Now we can initialize the JPEG decompression object.
|
|
||||||
jpeg_create_decompress(&cinfo);
|
|
||||||
|
|
||||||
// Step 2: specify data source (eg, a file)
|
|
||||||
jpeg_stdio_src(&cinfo, in);
|
|
||||||
|
|
||||||
// Step 3: read file parameters with jpeg_read_header()
|
|
||||||
(void) jpeg_read_header(&cinfo, TRUE);
|
|
||||||
|
|
||||||
// We can ignore the return value from jpeg_read_header since
|
|
||||||
// (a) suspension is not possible with the stdio data source, and
|
|
||||||
// (b) we passed TRUE to reject a tables-only JPEG file as an error.
|
|
||||||
|
|
||||||
// Step 4: set parameters for decompression
|
|
||||||
|
|
||||||
// In this example, we don't need to change any of the defaults set by
|
|
||||||
// jpeg_read_header(), so we do nothing here.
|
|
||||||
|
|
||||||
// Step 5: Start decompressor
|
|
||||||
|
|
||||||
(void) jpeg_start_decompress(&cinfo);
|
|
||||||
// We can ignore the return value since suspension is not possible
|
|
||||||
// with the stdio data source.
|
|
||||||
|
|
||||||
// We may need to do some setup of our own at this point before reading
|
|
||||||
// the data. After jpeg_start_decompress() we have the correct scaled
|
|
||||||
// output image dimensions available, as well as the output colormap
|
|
||||||
// if we asked for color quantization.
|
|
||||||
// In this example, we need to make an output work buffer of the right size.
|
|
||||||
// JSAMPLEs per row in output buffer
|
|
||||||
row_stride = cinfo.output_width * cinfo.output_components;
|
|
||||||
// Make a one-row-high sample array that will go away when done with image
|
|
||||||
buffer = (*cinfo.mem->alloc_sarray)
|
|
||||||
((j_common_ptr) & cinfo, JPOOL_IMAGE, row_stride, 1);
|
|
||||||
|
|
||||||
// Step 6: while (scan lines remain to be read)
|
|
||||||
// jpeg_read_scanlines(...);
|
|
||||||
|
|
||||||
// Here we use the library's state variable cinfo.output_scanline as the
|
|
||||||
// loop counter, so that we don't have to keep track ourselves.
|
|
||||||
|
|
||||||
int format = GL_RGB;
|
|
||||||
if (cinfo.output_components == 1)
|
|
||||||
format = GL_LUMINANCE;
|
|
||||||
|
|
||||||
img = new Image(format, cinfo.image_width, cinfo.image_height);
|
|
||||||
|
|
||||||
// cont = cinfo.output_height - 1;
|
|
||||||
cont = 0;
|
|
||||||
while (cinfo.output_scanline < cinfo.output_height)
|
|
||||||
{
|
|
||||||
// jpeg_read_scanlines expects an array of pointers to scanlines.
|
|
||||||
// Here the array is only one element long, but you could ask for
|
|
||||||
// more than one scanline at a time if that's more convenient.
|
|
||||||
(void) jpeg_read_scanlines(&cinfo, buffer, 1);
|
|
||||||
|
|
||||||
// Assume put_scanline_someplace wants a pointer and sample count.
|
|
||||||
// put_scanline_someplace(buffer[0], row_stride);
|
|
||||||
memcpy(img->getPixelRow(cont), buffer[0], row_stride);
|
|
||||||
cont++;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Step 7: Finish decompression
|
|
||||||
|
|
||||||
(void) jpeg_finish_decompress(&cinfo);
|
|
||||||
// We can ignore the return value since suspension is not possible
|
|
||||||
// with the stdio data source.
|
|
||||||
|
|
||||||
// Step 8: Release JPEG decompression object
|
|
||||||
|
|
||||||
// This is an important step since it will release a good deal of memory.
|
|
||||||
jpeg_destroy_decompress(&cinfo);
|
|
||||||
|
|
||||||
// After finish_decompress, we can close the input file.
|
|
||||||
// Here we postpone it until after no more JPEG errors are possible,
|
|
||||||
// so as to simplify the setjmp error logic above. (Actually, I don't
|
|
||||||
// think that jpeg_destroy can do an error exit, but why assume anything...
|
|
||||||
|
|
||||||
fclose(in);
|
|
||||||
|
|
||||||
// At this point you may want to check to see whether any corrupt-data
|
|
||||||
// warnings occurred (test whether jerr.pub.num_warnings is nonzero).
|
|
||||||
|
|
||||||
return img;
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
void PNGReadData(png_structp png_ptr, png_bytep data, png_size_t length)
|
|
||||||
{
|
|
||||||
auto* fp = (FILE*) png_get_io_ptr(png_ptr);
|
|
||||||
if (fread((void*) data, 1, length, fp) != length)
|
|
||||||
cerr << "Error reading PNG data";
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
Image* LoadPNGImage(const fs::path& filename)
|
|
||||||
{
|
|
||||||
char header[8];
|
|
||||||
png_structp png_ptr;
|
|
||||||
png_infop info_ptr;
|
|
||||||
png_uint_32 width, height;
|
|
||||||
int bit_depth, color_type, interlace_type;
|
|
||||||
Image* img = nullptr;
|
|
||||||
png_bytep* row_pointers = nullptr;
|
|
||||||
|
|
||||||
#ifdef _WIN32
|
|
||||||
FILE *fp = _wfopen(filename.c_str(), L"rb");
|
|
||||||
#else
|
|
||||||
FILE *fp = fopen(filename.c_str(), "rb");
|
|
||||||
#endif
|
|
||||||
if (fp == nullptr)
|
|
||||||
{
|
|
||||||
fmt::fprintf(clog, _("Error opening image file %s\n"), filename);
|
|
||||||
return nullptr;
|
|
||||||
}
|
|
||||||
|
|
||||||
size_t elements_read;
|
|
||||||
elements_read = fread(header, 1, sizeof(header), fp);
|
|
||||||
if (elements_read == 0 || png_sig_cmp((unsigned char*) header, 0, sizeof(header)))
|
|
||||||
{
|
|
||||||
fmt::fprintf(clog, _("Error: %s is not a PNG file.\n"), filename);
|
|
||||||
fclose(fp);
|
|
||||||
return nullptr;
|
|
||||||
}
|
|
||||||
|
|
||||||
png_ptr = png_create_read_struct(PNG_LIBPNG_VER_STRING,
|
|
||||||
nullptr, nullptr, nullptr);
|
|
||||||
if (png_ptr == nullptr)
|
|
||||||
{
|
|
||||||
fclose(fp);
|
|
||||||
return nullptr;
|
|
||||||
}
|
|
||||||
|
|
||||||
info_ptr = png_create_info_struct(png_ptr);
|
|
||||||
if (info_ptr == nullptr)
|
|
||||||
{
|
|
||||||
fclose(fp);
|
|
||||||
png_destroy_read_struct(&png_ptr, (png_infopp) nullptr, (png_infopp) nullptr);
|
|
||||||
return nullptr;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (setjmp(png_jmpbuf(png_ptr)))
|
|
||||||
{
|
|
||||||
fclose(fp);
|
|
||||||
delete img;
|
|
||||||
png_destroy_read_struct(&png_ptr, &info_ptr, (png_infopp) nullptr);
|
|
||||||
fmt::fprintf(clog, _("Error reading PNG image file %s\n"), filename);
|
|
||||||
return nullptr;
|
|
||||||
}
|
|
||||||
|
|
||||||
// png_init_io(png_ptr, fp);
|
|
||||||
png_set_read_fn(png_ptr, (void*) fp, PNGReadData);
|
|
||||||
png_set_sig_bytes(png_ptr, sizeof(header));
|
|
||||||
|
|
||||||
png_read_info(png_ptr, info_ptr);
|
|
||||||
|
|
||||||
png_get_IHDR(png_ptr, info_ptr,
|
|
||||||
&width, &height, &bit_depth,
|
|
||||||
&color_type, &interlace_type,
|
|
||||||
nullptr, nullptr);
|
|
||||||
|
|
||||||
GLenum glformat = GL_RGB;
|
|
||||||
switch (color_type)
|
|
||||||
{
|
|
||||||
case PNG_COLOR_TYPE_GRAY:
|
|
||||||
glformat = GL_LUMINANCE;
|
|
||||||
break;
|
|
||||||
case PNG_COLOR_TYPE_GRAY_ALPHA:
|
|
||||||
glformat = GL_LUMINANCE_ALPHA;
|
|
||||||
break;
|
|
||||||
case PNG_COLOR_TYPE_RGB:
|
|
||||||
glformat = GL_RGB;
|
|
||||||
break;
|
|
||||||
case PNG_COLOR_TYPE_PALETTE:
|
|
||||||
case PNG_COLOR_TYPE_RGB_ALPHA:
|
|
||||||
glformat = GL_RGBA;
|
|
||||||
break;
|
|
||||||
default:
|
|
||||||
// badness
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
|
|
||||||
img = new Image(glformat, width, height);
|
|
||||||
|
|
||||||
// TODO: consider using paletted textures if they're available
|
|
||||||
if (color_type == PNG_COLOR_TYPE_PALETTE)
|
|
||||||
{
|
|
||||||
png_set_palette_to_rgb(png_ptr);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (color_type == PNG_COLOR_TYPE_GRAY && bit_depth < 8)
|
|
||||||
{
|
|
||||||
png_set_expand_gray_1_2_4_to_8(png_ptr);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (png_get_valid(png_ptr, info_ptr, PNG_INFO_tRNS))
|
|
||||||
{
|
|
||||||
png_set_tRNS_to_alpha(png_ptr);
|
|
||||||
}
|
|
||||||
|
|
||||||
// TODO: consider passing images with < 8 bits/component to
|
|
||||||
// GL without expanding
|
|
||||||
if (bit_depth == 16)
|
|
||||||
png_set_strip_16(png_ptr);
|
|
||||||
else if (bit_depth < 8)
|
|
||||||
png_set_packing(png_ptr);
|
|
||||||
|
|
||||||
row_pointers = new png_bytep[height];
|
|
||||||
for (unsigned int i = 0; i < height; i++)
|
|
||||||
row_pointers[i] = (png_bytep) img->getPixelRow(i);
|
|
||||||
|
|
||||||
png_read_image(png_ptr, row_pointers);
|
|
||||||
|
|
||||||
delete[] row_pointers;
|
|
||||||
|
|
||||||
png_read_end(png_ptr, nullptr);
|
|
||||||
png_destroy_read_struct(&png_ptr, &info_ptr, nullptr);
|
|
||||||
|
|
||||||
fclose(fp);
|
|
||||||
|
|
||||||
return img;
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
// BMP file definitions--can't use windows.h because we might not be
|
|
||||||
// built on Windows!
|
|
||||||
typedef struct
|
|
||||||
{
|
|
||||||
unsigned char b;
|
|
||||||
unsigned char m;
|
|
||||||
unsigned int size;
|
|
||||||
unsigned int reserved;
|
|
||||||
unsigned int offset;
|
|
||||||
} BMPFileHeader;
|
|
||||||
|
|
||||||
typedef struct
|
|
||||||
{
|
|
||||||
unsigned int size;
|
|
||||||
int width;
|
|
||||||
int height;
|
|
||||||
unsigned short planes;
|
|
||||||
unsigned short bpp;
|
|
||||||
unsigned int compression;
|
|
||||||
unsigned int imageSize;
|
|
||||||
int widthPPM;
|
|
||||||
int heightPPM;
|
|
||||||
unsigned int colorsUsed;
|
|
||||||
unsigned int colorsImportant;
|
|
||||||
} BMPImageHeader;
|
|
||||||
|
|
||||||
|
|
||||||
static int readInt(ifstream& in)
|
|
||||||
{
|
|
||||||
unsigned char b[4];
|
|
||||||
in.read(reinterpret_cast<char*>(b), 4);
|
|
||||||
return ((int) b[3] << 24) + ((int) b[2] << 16)
|
|
||||||
+ ((int) b[1] << 8) + (int) b[0];
|
|
||||||
}
|
|
||||||
|
|
||||||
static short readShort(ifstream& in)
|
|
||||||
{
|
|
||||||
unsigned char b[2];
|
|
||||||
in.read(reinterpret_cast<char*>(b), 2);
|
|
||||||
return ((short) b[1] << 8) + (short) b[0];
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
static Image* LoadBMPImage(ifstream& in)
|
|
||||||
{
|
|
||||||
BMPFileHeader fileHeader;
|
|
||||||
BMPImageHeader imageHeader;
|
|
||||||
unsigned char* pixels;
|
|
||||||
|
|
||||||
in >> fileHeader.b;
|
|
||||||
in >> fileHeader.m;
|
|
||||||
fileHeader.size = readInt(in);
|
|
||||||
fileHeader.reserved = readInt(in);
|
|
||||||
fileHeader.offset = readInt(in);
|
|
||||||
|
|
||||||
if (fileHeader.b != 'B' || fileHeader.m != 'M')
|
|
||||||
return nullptr;
|
|
||||||
|
|
||||||
imageHeader.size = readInt(in);
|
|
||||||
imageHeader.width = readInt(in);
|
|
||||||
imageHeader.height = readInt(in);
|
|
||||||
imageHeader.planes = readShort(in);
|
|
||||||
imageHeader.bpp = readShort(in);
|
|
||||||
imageHeader.compression = readInt(in);
|
|
||||||
imageHeader.imageSize = readInt(in);
|
|
||||||
imageHeader.widthPPM = readInt(in);
|
|
||||||
imageHeader.heightPPM = readInt(in);
|
|
||||||
imageHeader.colorsUsed = readInt(in);
|
|
||||||
imageHeader.colorsImportant = readInt(in);
|
|
||||||
|
|
||||||
if (imageHeader.width <= 0 || imageHeader.height <= 0)
|
|
||||||
return nullptr;
|
|
||||||
|
|
||||||
// We currently don't support compressed BMPs
|
|
||||||
if (imageHeader.compression != 0)
|
|
||||||
return nullptr;
|
|
||||||
// We don't handle 1-, 2-, or 4-bpp images
|
|
||||||
if (imageHeader.bpp != 8 && imageHeader.bpp != 24 && imageHeader.bpp != 32)
|
|
||||||
return nullptr;
|
|
||||||
|
|
||||||
unsigned char* palette = nullptr;
|
|
||||||
if (imageHeader.bpp == 8)
|
|
||||||
{
|
|
||||||
fmt::fprintf(cout, "Reading %d color palette\n", imageHeader.colorsUsed);
|
|
||||||
palette = new unsigned char[imageHeader.colorsUsed * 4];
|
|
||||||
in.read(reinterpret_cast<char*>(palette), imageHeader.colorsUsed * 4);
|
|
||||||
}
|
|
||||||
|
|
||||||
in.seekg(fileHeader.offset, ios::beg);
|
|
||||||
|
|
||||||
unsigned int bytesPerRow =
|
|
||||||
(imageHeader.width * imageHeader.bpp / 8 + 1) & ~1;
|
|
||||||
unsigned int imageBytes = bytesPerRow * imageHeader.height;
|
|
||||||
|
|
||||||
// slurp the image data
|
|
||||||
pixels = new unsigned char[imageBytes];
|
|
||||||
in.read(reinterpret_cast<char*>(pixels), imageBytes);
|
|
||||||
|
|
||||||
// check for truncated file
|
|
||||||
|
|
||||||
auto* img = new Image(GL_RGB, imageHeader.width, imageHeader.height);
|
|
||||||
|
|
||||||
// Copy the image and perform any necessary conversions
|
|
||||||
for (int y = 0; y < imageHeader.height; y++)
|
|
||||||
{
|
|
||||||
unsigned char* src = &pixels[y * bytesPerRow];
|
|
||||||
unsigned char* dst = img->getPixelRow(y);
|
|
||||||
|
|
||||||
switch (imageHeader.bpp)
|
|
||||||
{
|
|
||||||
case 8:
|
|
||||||
for (int x = 0; x < imageHeader.width; x++)
|
|
||||||
{
|
|
||||||
unsigned char* color = palette + (*src << 2);
|
|
||||||
dst[0] = color[2];
|
|
||||||
dst[1] = color[1];
|
|
||||||
dst[2] = color[0];
|
|
||||||
src++;
|
|
||||||
dst += 3;
|
|
||||||
}
|
|
||||||
break;
|
|
||||||
case 24:
|
|
||||||
for (int x = 0; x < imageHeader.width; x++)
|
|
||||||
{
|
|
||||||
dst[0] = src[2];
|
|
||||||
dst[1] = src[1];
|
|
||||||
dst[2] = src[0];
|
|
||||||
src += 3;
|
|
||||||
dst += 3;
|
|
||||||
}
|
|
||||||
break;
|
|
||||||
case 32:
|
|
||||||
for (int x = 0; x < imageHeader.width; x++)
|
|
||||||
{
|
|
||||||
dst[0] = src[2];
|
|
||||||
dst[1] = src[1];
|
|
||||||
dst[2] = src[0];
|
|
||||||
src += 4;
|
|
||||||
dst += 3;
|
|
||||||
}
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
delete[] pixels;
|
|
||||||
delete[] palette;
|
|
||||||
|
|
||||||
return img;
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
Image* LoadBMPImage(const fs::path& filename)
|
|
||||||
{
|
|
||||||
ifstream bmpFile(filename.string(), ios::in | ios::binary);
|
|
||||||
|
|
||||||
if (bmpFile.good())
|
|
||||||
{
|
|
||||||
Image* img = LoadBMPImage(bmpFile);
|
|
||||||
bmpFile.close();
|
|
||||||
return img;
|
|
||||||
}
|
|
||||||
|
|
||||||
return nullptr;
|
|
||||||
}
|
|
||||||
|
|
|
@ -1,16 +1,15 @@
|
||||||
// image.h
|
// image.h
|
||||||
//
|
//
|
||||||
// Copyright (C) 2001, Chris Laurel
|
// Copyright (C) 2001-present, the Celestia Development Team
|
||||||
|
// Original version by Chris Laurel <claurel@shatters.net>
|
||||||
//
|
//
|
||||||
// This program is free software; you can redistribute it and/or
|
// This program is free software; you can redistribute it and/or
|
||||||
// modify it under the terms of the GNU General Public License
|
// modify it under the terms of the GNU General Public License
|
||||||
// as published by the Free Software Foundation; either version 2
|
// as published by the Free Software Foundation; either version 2
|
||||||
// of the License, or (at your option) any later version.
|
// of the License, or (at your option) any later version.
|
||||||
|
|
||||||
#ifndef _CELENGINE_IMAGE_H_
|
#pragma once
|
||||||
#define _CELENGINE_IMAGE_H_
|
|
||||||
|
|
||||||
#include <string>
|
|
||||||
#include <celcompat/filesystem.h>
|
#include <celcompat/filesystem.h>
|
||||||
|
|
||||||
// The image class supports multiple GL formats, including compressed ones.
|
// The image class supports multiple GL formats, including compressed ones.
|
||||||
|
@ -43,7 +42,8 @@ class Image
|
||||||
|
|
||||||
Image* computeNormalMap(float scale, bool wrap) const;
|
Image* computeNormalMap(float scale, bool wrap) const;
|
||||||
|
|
||||||
enum {
|
enum
|
||||||
|
{
|
||||||
ColorChannel = 1,
|
ColorChannel = 1,
|
||||||
AlphaChannel = 2
|
AlphaChannel = 2
|
||||||
};
|
};
|
||||||
|
@ -59,12 +59,4 @@ class Image
|
||||||
unsigned char* pixels{ nullptr };
|
unsigned char* pixels{ nullptr };
|
||||||
};
|
};
|
||||||
|
|
||||||
extern Image* LoadJPEGImage(const fs::path& filename,
|
Image* LoadImageFromFile(const fs::path& filename);
|
||||||
int channels = Image::ColorChannel);
|
|
||||||
extern Image* LoadBMPImage(const fs::path& filename);
|
|
||||||
extern Image* LoadPNGImage(const fs::path& filename);
|
|
||||||
extern Image* LoadDDSImage(const fs::path& filename);
|
|
||||||
|
|
||||||
extern Image* LoadImageFromFile(const fs::path& filename);
|
|
||||||
|
|
||||||
#endif // _CELENGINE_IMAGE_H_
|
|
||||||
|
|
|
@ -52,6 +52,7 @@ add_library(celestia SHARED ${CELESTIA_SOURCES}
|
||||||
$<TARGET_OBJECTS:celcompat>
|
$<TARGET_OBJECTS:celcompat>
|
||||||
$<TARGET_OBJECTS:celengine>
|
$<TARGET_OBJECTS:celengine>
|
||||||
$<TARGET_OBJECTS:celephem>
|
$<TARGET_OBJECTS:celephem>
|
||||||
|
$<TARGET_OBJECTS:celimage>
|
||||||
$<TARGET_OBJECTS:celmath>
|
$<TARGET_OBJECTS:celmath>
|
||||||
$<TARGET_OBJECTS:celmodel>
|
$<TARGET_OBJECTS:celmodel>
|
||||||
$<TARGET_OBJECTS:celttf>
|
$<TARGET_OBJECTS:celttf>
|
||||||
|
|
|
@ -4688,22 +4688,11 @@ bool CelestiaCore::saveScreenShot(const fs::path& filename, ContentType type) co
|
||||||
array<int, 4> viewport;
|
array<int, 4> viewport;
|
||||||
getRenderer()->getViewport(viewport);
|
getRenderer()->getViewport(viewport);
|
||||||
|
|
||||||
if (type == Content_JPEG)
|
return CaptureBufferToFile(filename,
|
||||||
{
|
|
||||||
return CaptureGLBufferToJPEG(filename,
|
|
||||||
viewport[0], viewport[1],
|
viewport[0], viewport[1],
|
||||||
viewport[2], viewport[3],
|
viewport[2], viewport[3],
|
||||||
getRenderer());
|
getRenderer(),
|
||||||
}
|
type);
|
||||||
if (type == Content_PNG)
|
|
||||||
{
|
|
||||||
return CaptureGLBufferToPNG(filename,
|
|
||||||
viewport[0], viewport[1],
|
|
||||||
viewport[2], viewport[3],
|
|
||||||
getRenderer());
|
|
||||||
}
|
|
||||||
|
|
||||||
return false;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void CelestiaCore::setLogFile(fs::path &fn)
|
void CelestiaCore::setLogFile(fs::path &fn)
|
||||||
|
|
|
@ -1,30 +1,37 @@
|
||||||
// imagecapture.cpp
|
// imagecapture.cpp
|
||||||
//
|
//
|
||||||
// Copyright (C) 2001, Chris Laurel <claurel@shatters.net>
|
// Copyright (C) 2001-present, the Celestia Development Team
|
||||||
|
// Original version by Chris Laurel <claurel@shatters.net>
|
||||||
//
|
//
|
||||||
// This program is free software; you can redistribute it and/or
|
// This program is free software; you can redistribute it and/or
|
||||||
// modify it under the terms of the GNU General Public License
|
// modify it under the terms of the GNU General Public License
|
||||||
// as published by the Free Software Foundation; either version 2
|
// as published by the Free Software Foundation; either version 2
|
||||||
// of the License, or (at your option) any later version.
|
// of the License, or (at your option) any later version.
|
||||||
|
|
||||||
#include <config.h>
|
#include <iostream>
|
||||||
#include <celutil/debug.h>
|
#include <memory>
|
||||||
|
#include <fmt/format.h>
|
||||||
|
#include <celengine/render.h>
|
||||||
|
#include <celimage/imageformats.h>
|
||||||
|
#include <celutil/gettext.h>
|
||||||
#include "imagecapture.h"
|
#include "imagecapture.h"
|
||||||
|
|
||||||
extern "C" {
|
using fmt::print;
|
||||||
#include <jpeglib.h>
|
using std::cerr;
|
||||||
}
|
using std::unique_ptr;
|
||||||
#include <png.h>
|
|
||||||
#include <zlib.h>
|
|
||||||
|
|
||||||
using namespace std;
|
bool CaptureBufferToFile(const fs::path& filename,
|
||||||
|
|
||||||
|
|
||||||
bool CaptureGLBufferToJPEG(const fs::path& filename,
|
|
||||||
int x, int y,
|
int x, int y,
|
||||||
int width, int height,
|
int width, int height,
|
||||||
const Renderer *renderer)
|
const Renderer *renderer,
|
||||||
|
ContentType type)
|
||||||
{
|
{
|
||||||
|
if (type != Content_JPEG && type != Content_PNG)
|
||||||
|
{
|
||||||
|
print(cerr, "Unsupported image type: {}!\n", filename.string());
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
#ifdef GL_ES
|
#ifdef GL_ES
|
||||||
int rowStride = width * 4;
|
int rowStride = width * 4;
|
||||||
int imageSize = height * rowStride;
|
int imageSize = height * rowStride;
|
||||||
|
@ -34,196 +41,25 @@ bool CaptureGLBufferToJPEG(const fs::path& filename,
|
||||||
int imageSize = height * rowStride;
|
int imageSize = height * rowStride;
|
||||||
Renderer::PixelFormat format = Renderer::PixelFormat::RGB;
|
Renderer::PixelFormat format = Renderer::PixelFormat::RGB;
|
||||||
#endif
|
#endif
|
||||||
auto* pixels = new unsigned char[imageSize];
|
auto pixels = unique_ptr<unsigned char[]>(new unsigned char[imageSize]);
|
||||||
|
|
||||||
if (!renderer->captureFrame(x, y, width, height,
|
if (!renderer->captureFrame(x, y, width, height,
|
||||||
format,
|
format, pixels.get(), true))
|
||||||
pixels, true))
|
|
||||||
{
|
{
|
||||||
|
print(cerr, _("Unable to capture a frame!\n"));
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
FILE* out;
|
bool removeAlpha = format == Renderer::PixelFormat::RGBA;
|
||||||
#ifdef _WIN32
|
|
||||||
out = _wfopen(filename.c_str(), L"wb");
|
switch (type)
|
||||||
#else
|
|
||||||
out = fopen(filename.c_str(), "wb");
|
|
||||||
#endif
|
|
||||||
if (out == nullptr)
|
|
||||||
{
|
{
|
||||||
DPRINTF(LOG_LEVEL_ERROR, "Can't open screen capture file '%s'\n", filename);
|
case Content_JPEG:
|
||||||
delete[] pixels;
|
return SaveJPEGImage(filename, width, height, rowStride, pixels.get(), removeAlpha);
|
||||||
|
case Content_PNG:
|
||||||
|
return SavePNGImage(filename, width, height, rowStride, pixels.get(), removeAlpha);
|
||||||
|
default:
|
||||||
|
break;
|
||||||
|
}
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
struct jpeg_compress_struct cinfo;
|
|
||||||
|
|
||||||
struct jpeg_error_mgr jerr;
|
|
||||||
JSAMPROW row[1];
|
|
||||||
|
|
||||||
cinfo.err = jpeg_std_error(&jerr);
|
|
||||||
jpeg_create_compress(&cinfo);
|
|
||||||
|
|
||||||
jpeg_stdio_dest(&cinfo, out);
|
|
||||||
|
|
||||||
cinfo.image_width = width;
|
|
||||||
cinfo.image_height = height;
|
|
||||||
cinfo.input_components = 3;
|
|
||||||
cinfo.in_color_space = JCS_RGB;
|
|
||||||
|
|
||||||
jpeg_set_defaults(&cinfo);
|
|
||||||
|
|
||||||
jpeg_set_quality(&cinfo, 90, TRUE);
|
|
||||||
|
|
||||||
jpeg_start_compress(&cinfo, TRUE);
|
|
||||||
|
|
||||||
while (cinfo.next_scanline < cinfo.image_height)
|
|
||||||
{
|
|
||||||
unsigned char *rowHead = &pixels[rowStride * (cinfo.image_height - cinfo.next_scanline - 1)];
|
|
||||||
// Strip alpha values if we are in RGBA format
|
|
||||||
if (format == Renderer::PixelFormat::RGBA)
|
|
||||||
{
|
|
||||||
for (int x = 0; x < width; x++)
|
|
||||||
{
|
|
||||||
const unsigned char* pixelIn = &rowHead[x * 4];
|
|
||||||
unsigned char* pixelOut = &rowHead[x * 3];
|
|
||||||
pixelOut[0] = pixelIn[0];
|
|
||||||
pixelOut[1] = pixelIn[1];
|
|
||||||
pixelOut[2] = pixelIn[2];
|
|
||||||
}
|
|
||||||
}
|
|
||||||
row[0] = rowHead;
|
|
||||||
(void) jpeg_write_scanlines(&cinfo, row, 1);
|
|
||||||
}
|
|
||||||
|
|
||||||
jpeg_finish_compress(&cinfo);
|
|
||||||
fclose(out);
|
|
||||||
jpeg_destroy_compress(&cinfo);
|
|
||||||
|
|
||||||
delete[] pixels;
|
|
||||||
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
void PNGWriteData(png_structp png_ptr, png_bytep data, png_size_t length)
|
|
||||||
{
|
|
||||||
auto* fp = (FILE*) png_get_io_ptr(png_ptr);
|
|
||||||
fwrite((void*) data, 1, length, fp);
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
bool CaptureGLBufferToPNG(const fs::path& filename,
|
|
||||||
int x, int y,
|
|
||||||
int width, int height,
|
|
||||||
const Renderer *renderer)
|
|
||||||
{
|
|
||||||
#ifdef GL_ES
|
|
||||||
int rowStride = width * 4;
|
|
||||||
int imageSize = height * rowStride;
|
|
||||||
Renderer::PixelFormat format = Renderer::PixelFormat::RGBA;
|
|
||||||
#else
|
|
||||||
int rowStride = (width * 3 + 3) & ~0x3;
|
|
||||||
int imageSize = height * rowStride;
|
|
||||||
Renderer::PixelFormat format = Renderer::PixelFormat::RGB;
|
|
||||||
#endif
|
|
||||||
auto* pixels = new unsigned char[imageSize];
|
|
||||||
|
|
||||||
if (!renderer->captureFrame(x, y, width, height,
|
|
||||||
format,
|
|
||||||
pixels, true))
|
|
||||||
{
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
#ifdef _WIN32
|
|
||||||
FILE* out = _wfopen(filename.c_str(), L"wb");
|
|
||||||
#else
|
|
||||||
FILE* out = fopen(filename.c_str(), "wb");
|
|
||||||
#endif
|
|
||||||
if (out == nullptr)
|
|
||||||
{
|
|
||||||
DPRINTF(LOG_LEVEL_ERROR, "Can't open screen capture file '%s'\n", filename);
|
|
||||||
delete[] pixels;
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
auto* row_pointers = new png_bytep[height];
|
|
||||||
for (int i = 0; i < height; i++)
|
|
||||||
{
|
|
||||||
unsigned char *rowHead = &pixels[rowStride * (height - i - 1)];
|
|
||||||
// Strip alpha values if we are in RGBA format
|
|
||||||
if (format == Renderer::PixelFormat::RGBA)
|
|
||||||
{
|
|
||||||
for (int x = 0; x < width; x++)
|
|
||||||
{
|
|
||||||
const unsigned char* pixelIn = &rowHead[x * 4];
|
|
||||||
unsigned char* pixelOut = &rowHead[x * 3];
|
|
||||||
pixelOut[0] = pixelIn[0];
|
|
||||||
pixelOut[1] = pixelIn[1];
|
|
||||||
pixelOut[2] = pixelIn[2];
|
|
||||||
}
|
|
||||||
}
|
|
||||||
row_pointers[i] = (png_bytep) rowHead;
|
|
||||||
}
|
|
||||||
|
|
||||||
png_structp png_ptr;
|
|
||||||
png_infop info_ptr;
|
|
||||||
|
|
||||||
png_ptr = png_create_write_struct(PNG_LIBPNG_VER_STRING,
|
|
||||||
nullptr, nullptr, nullptr);
|
|
||||||
|
|
||||||
if (png_ptr == nullptr)
|
|
||||||
{
|
|
||||||
DPRINTF(LOG_LEVEL_ERROR, "Screen capture: error allocating png_ptr\n");
|
|
||||||
fclose(out);
|
|
||||||
delete[] pixels;
|
|
||||||
delete[] row_pointers;
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
info_ptr = png_create_info_struct(png_ptr);
|
|
||||||
if (info_ptr == nullptr)
|
|
||||||
{
|
|
||||||
DPRINTF(LOG_LEVEL_ERROR, "Screen capture: error allocating info_ptr\n");
|
|
||||||
fclose(out);
|
|
||||||
delete[] pixels;
|
|
||||||
delete[] row_pointers;
|
|
||||||
png_destroy_write_struct(&png_ptr, (png_infopp) nullptr);
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (setjmp(png_jmpbuf(png_ptr)))
|
|
||||||
{
|
|
||||||
DPRINTF(LOG_LEVEL_ERROR, "Error writing PNG file '%s'\n", filename);
|
|
||||||
fclose(out);
|
|
||||||
delete[] pixels;
|
|
||||||
delete[] row_pointers;
|
|
||||||
png_destroy_write_struct(&png_ptr, &info_ptr);
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
// png_init_io(png_ptr, out);
|
|
||||||
png_set_write_fn(png_ptr, (void*) out, PNGWriteData, nullptr);
|
|
||||||
|
|
||||||
png_set_compression_level(png_ptr, Z_BEST_COMPRESSION);
|
|
||||||
png_set_IHDR(png_ptr, info_ptr,
|
|
||||||
width, height,
|
|
||||||
8,
|
|
||||||
PNG_COLOR_TYPE_RGB,
|
|
||||||
PNG_INTERLACE_NONE,
|
|
||||||
PNG_COMPRESSION_TYPE_DEFAULT,
|
|
||||||
PNG_FILTER_TYPE_DEFAULT);
|
|
||||||
|
|
||||||
png_write_info(png_ptr, info_ptr);
|
|
||||||
png_write_image(png_ptr, row_pointers);
|
|
||||||
png_write_end(png_ptr, info_ptr);
|
|
||||||
|
|
||||||
// Clean up everything . . .
|
|
||||||
png_destroy_write_struct(&png_ptr, &info_ptr);
|
|
||||||
delete[] row_pointers;
|
|
||||||
delete[] pixels;
|
|
||||||
fclose(out);
|
|
||||||
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
|
@ -1,26 +1,22 @@
|
||||||
// imagecapture.h
|
// imagecapture.h
|
||||||
//
|
//
|
||||||
// Copyright (C) 2001, Chris Laurel <claurel@shatters.net>
|
// Copyright (C) 2001-present, the Celestia Development Team
|
||||||
|
// Original version by Chris Laurel <claurel@shatters.net>
|
||||||
//
|
//
|
||||||
// This program is free software; you can redistribute it and/or
|
// This program is free software; you can redistribute it and/or
|
||||||
// modify it under the terms of the GNU General Public License
|
// modify it under the terms of the GNU General Public License
|
||||||
// as published by the Free Software Foundation; either version 2
|
// as published by the Free Software Foundation; either version 2
|
||||||
// of the License, or (at your option) any later version.
|
// of the License, or (at your option) any later version.
|
||||||
|
|
||||||
#ifndef _IMAGECAPTURE_H_
|
#pragma once
|
||||||
#define _IMAGECAPTURE_H_
|
|
||||||
|
|
||||||
#include <celcompat/filesystem.h>
|
#include <celcompat/filesystem.h>
|
||||||
#include <celengine/render.h>
|
#include <celutil/filetype.h>
|
||||||
|
|
||||||
|
class Renderer;
|
||||||
|
|
||||||
extern bool CaptureGLBufferToJPEG(const fs::path& filename,
|
bool CaptureBufferToFile(const fs::path& filename,
|
||||||
int x, int y,
|
int x, int y,
|
||||||
int width, int height,
|
int width, int height,
|
||||||
const Renderer *renderer);
|
const Renderer *renderer,
|
||||||
extern bool CaptureGLBufferToPNG(const fs::path& filename,
|
ContentType type);
|
||||||
int x, int y,
|
|
||||||
int width, int height,
|
|
||||||
const Renderer *renderer);
|
|
||||||
|
|
||||||
#endif // _IMAGECAPTURE_H_
|
|
||||||
|
|
|
@ -0,0 +1,12 @@
|
||||||
|
set(CELIMAGE_SOURCES
|
||||||
|
bmp.cpp
|
||||||
|
dds.cpp
|
||||||
|
dds_decompress.cpp
|
||||||
|
dds_decompress.h
|
||||||
|
imageformats.h
|
||||||
|
jpeg.cpp
|
||||||
|
png.cpp
|
||||||
|
)
|
||||||
|
|
||||||
|
add_library(celimage OBJECT ${CELIMAGE_SOURCES})
|
||||||
|
cotire(celimage)
|
|
@ -0,0 +1,192 @@
|
||||||
|
// bmp.cpp
|
||||||
|
//
|
||||||
|
// Copyright (C) 2001-present, the Celestia Development Team
|
||||||
|
// Original version by Chris Laurel <claurel@shatters.net>
|
||||||
|
//
|
||||||
|
// This program is free software; you can redistribute it and/or
|
||||||
|
// modify it under the terms of the GNU General Public License
|
||||||
|
// as published by the Free Software Foundation; either version 2
|
||||||
|
// of the License, or (at your option) any later version.
|
||||||
|
|
||||||
|
#include <cstdint>
|
||||||
|
#include <fstream> // ifstream
|
||||||
|
#include <iostream> // ios
|
||||||
|
#include <celengine/glsupport.h>
|
||||||
|
#include <celengine/image.h>
|
||||||
|
#include <celutil/bytes.h>
|
||||||
|
#include <celutil/debug.h>
|
||||||
|
|
||||||
|
using std::ifstream;
|
||||||
|
using std::ios;
|
||||||
|
|
||||||
|
namespace
|
||||||
|
{
|
||||||
|
// BMP file definitions--can't use windows.h because we might not be
|
||||||
|
// built on Windows!
|
||||||
|
typedef struct
|
||||||
|
{
|
||||||
|
unsigned char magic[2];
|
||||||
|
uint32_t size;
|
||||||
|
uint32_t reserved;
|
||||||
|
uint32_t offset;
|
||||||
|
} BMPFileHeader;
|
||||||
|
|
||||||
|
typedef struct
|
||||||
|
{
|
||||||
|
uint32_t size;
|
||||||
|
int32_t width;
|
||||||
|
int32_t height;
|
||||||
|
uint16_t planes;
|
||||||
|
uint16_t bpp;
|
||||||
|
uint32_t compression;
|
||||||
|
uint32_t imageSize;
|
||||||
|
int32_t widthPPM;
|
||||||
|
int32_t heightPPM;
|
||||||
|
uint32_t colorsUsed;
|
||||||
|
uint32_t colorsImportant;
|
||||||
|
} BMPImageHeader;
|
||||||
|
|
||||||
|
|
||||||
|
int32_t readInt32(ifstream& in)
|
||||||
|
{
|
||||||
|
uint8_t b[4];
|
||||||
|
in.read(reinterpret_cast<char*>(b), 4);
|
||||||
|
int32_t val = ((int32_t) b[3] << 24) +
|
||||||
|
((int32_t) b[2] << 16) +
|
||||||
|
((int32_t) b[1] << 8) +
|
||||||
|
((int32_t) b[0]);
|
||||||
|
LE_TO_CPU_INT32(val, val);
|
||||||
|
return val;
|
||||||
|
}
|
||||||
|
|
||||||
|
int16_t readInt16(ifstream& in)
|
||||||
|
{
|
||||||
|
uint8_t b[2];
|
||||||
|
in.read(reinterpret_cast<char*>(b), 2);
|
||||||
|
int16_t val = ((int16_t) b[1] << 8) + (int16_t) b[0];
|
||||||
|
LE_TO_CPU_INT16(val, val);
|
||||||
|
return val;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
Image* LoadBMPImage(ifstream& in)
|
||||||
|
{
|
||||||
|
BMPFileHeader fileHeader;
|
||||||
|
BMPImageHeader imageHeader;
|
||||||
|
uint8_t* pixels;
|
||||||
|
|
||||||
|
in >> fileHeader.magic[0];
|
||||||
|
in >> fileHeader.magic[1];
|
||||||
|
fileHeader.size = readInt32(in);
|
||||||
|
fileHeader.reserved = readInt32(in);
|
||||||
|
fileHeader.offset = readInt32(in);
|
||||||
|
|
||||||
|
if (fileHeader.magic[0] != 'B' || fileHeader.magic[1] != 'M')
|
||||||
|
return nullptr;
|
||||||
|
|
||||||
|
imageHeader.size = readInt32(in);
|
||||||
|
imageHeader.width = readInt32(in);
|
||||||
|
imageHeader.height = readInt32(in);
|
||||||
|
imageHeader.planes = readInt16(in);
|
||||||
|
imageHeader.bpp = readInt16(in);
|
||||||
|
imageHeader.compression = readInt32(in);
|
||||||
|
imageHeader.imageSize = readInt32(in);
|
||||||
|
imageHeader.widthPPM = readInt32(in);
|
||||||
|
imageHeader.heightPPM = readInt32(in);
|
||||||
|
imageHeader.colorsUsed = readInt32(in);
|
||||||
|
imageHeader.colorsImportant = readInt32(in);
|
||||||
|
|
||||||
|
if (imageHeader.width <= 0 || imageHeader.height <= 0)
|
||||||
|
return nullptr;
|
||||||
|
|
||||||
|
// We currently don't support compressed BMPs
|
||||||
|
if (imageHeader.compression != 0)
|
||||||
|
return nullptr;
|
||||||
|
// We don't handle 1-, 2-, or 4-bpp images
|
||||||
|
if (imageHeader.bpp != 8 && imageHeader.bpp != 24 && imageHeader.bpp != 32)
|
||||||
|
return nullptr;
|
||||||
|
|
||||||
|
uint8_t* palette = nullptr;
|
||||||
|
if (imageHeader.bpp == 8)
|
||||||
|
{
|
||||||
|
DPRINTF(LOG_LEVEL_DEBUG, "Reading %u color palette\n", imageHeader.colorsUsed);
|
||||||
|
palette = new uint8_t[imageHeader.colorsUsed * 4];
|
||||||
|
in.read(reinterpret_cast<char*>(palette), imageHeader.colorsUsed * 4);
|
||||||
|
}
|
||||||
|
|
||||||
|
in.seekg(fileHeader.offset, ios::beg);
|
||||||
|
|
||||||
|
uint32_t bytesPerRow =
|
||||||
|
(imageHeader.width * imageHeader.bpp / 8 + 1) & ~1;
|
||||||
|
uint32_t imageBytes = bytesPerRow * imageHeader.height;
|
||||||
|
|
||||||
|
// slurp the image data
|
||||||
|
pixels = new uint8_t[imageBytes];
|
||||||
|
in.read(reinterpret_cast<char*>(pixels), imageBytes);
|
||||||
|
|
||||||
|
// check for truncated file
|
||||||
|
|
||||||
|
auto* img = new Image(GL_RGB, imageHeader.width, imageHeader.height);
|
||||||
|
|
||||||
|
// Copy the image and perform any necessary conversions
|
||||||
|
for (int32_t y = 0; y < imageHeader.height; y++)
|
||||||
|
{
|
||||||
|
uint8_t* src = &pixels[y * bytesPerRow];
|
||||||
|
uint8_t* dst = img->getPixelRow(y);
|
||||||
|
|
||||||
|
switch (imageHeader.bpp)
|
||||||
|
{
|
||||||
|
case 8:
|
||||||
|
for (int32_t x = 0; x < imageHeader.width; x++)
|
||||||
|
{
|
||||||
|
uint8_t* color = palette + (*src << 2);
|
||||||
|
dst[0] = color[2];
|
||||||
|
dst[1] = color[1];
|
||||||
|
dst[2] = color[0];
|
||||||
|
src++;
|
||||||
|
dst += 3;
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
case 24:
|
||||||
|
for (int32_t x = 0; x < imageHeader.width; x++)
|
||||||
|
{
|
||||||
|
dst[0] = src[2];
|
||||||
|
dst[1] = src[1];
|
||||||
|
dst[2] = src[0];
|
||||||
|
src += 3;
|
||||||
|
dst += 3;
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
case 32:
|
||||||
|
for (int32_t x = 0; x < imageHeader.width; x++)
|
||||||
|
{
|
||||||
|
dst[0] = src[2];
|
||||||
|
dst[1] = src[1];
|
||||||
|
dst[2] = src[0];
|
||||||
|
src += 4;
|
||||||
|
dst += 3;
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
delete[] pixels;
|
||||||
|
delete[] palette;
|
||||||
|
|
||||||
|
return img;
|
||||||
|
}
|
||||||
|
} // anonymous namespace
|
||||||
|
|
||||||
|
Image* LoadBMPImage(const fs::path& filename)
|
||||||
|
{
|
||||||
|
ifstream bmpFile(filename.string(), ios::in | ios::binary);
|
||||||
|
|
||||||
|
if (bmpFile.good())
|
||||||
|
{
|
||||||
|
Image* img = LoadBMPImage(bmpFile);
|
||||||
|
bmpFile.close();
|
||||||
|
return img;
|
||||||
|
}
|
||||||
|
|
||||||
|
return nullptr;
|
||||||
|
}
|
|
@ -1,6 +1,7 @@
|
||||||
// dds.cpp
|
// dds.cpp
|
||||||
//
|
//
|
||||||
// Copyright (C) 2001, Chris Laurel
|
// Copyright (C) 2001-present, the Celestia Development Team
|
||||||
|
// Original version by Chris Laurel <claurel@shatters.net>
|
||||||
//
|
//
|
||||||
// This program is free software; you can redistribute it and/or
|
// This program is free software; you can redistribute it and/or
|
||||||
// modify it under the terms of the GNU General Public License
|
// modify it under the terms of the GNU General Public License
|
||||||
|
@ -11,10 +12,10 @@
|
||||||
#include <fstream>
|
#include <fstream>
|
||||||
#include <algorithm>
|
#include <algorithm>
|
||||||
#include <memory>
|
#include <memory>
|
||||||
|
#include <celengine/glsupport.h>
|
||||||
|
#include <celengine/image.h>
|
||||||
#include <celutil/debug.h>
|
#include <celutil/debug.h>
|
||||||
#include <celutil/bytes.h>
|
#include <celutil/bytes.h>
|
||||||
#include <celengine/image.h>
|
|
||||||
#include "glsupport.h"
|
|
||||||
#include "dds_decompress.h"
|
#include "dds_decompress.h"
|
||||||
|
|
||||||
using namespace celestia;
|
using namespace celestia;
|
|
@ -0,0 +1,30 @@
|
||||||
|
// imageformats.h
|
||||||
|
//
|
||||||
|
// Copyright (C) 2001-present, the Celestia Development Team
|
||||||
|
// Original version by Chris Laurel <claurel@shatters.net>
|
||||||
|
//
|
||||||
|
// This program is free software; you can redistribute it and/or
|
||||||
|
// modify it under the terms of the GNU General Public License
|
||||||
|
// as published by the Free Software Foundation; either version 2
|
||||||
|
// of the License, or (at your option) any later version.
|
||||||
|
|
||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include <celengine/image.h>
|
||||||
|
|
||||||
|
Image* LoadJPEGImage(const fs::path& filename,
|
||||||
|
int channels = Image::ColorChannel);
|
||||||
|
Image* LoadBMPImage(const fs::path& filename);
|
||||||
|
Image* LoadPNGImage(const fs::path& filename);
|
||||||
|
Image* LoadDDSImage(const fs::path& filename);
|
||||||
|
|
||||||
|
bool SaveJPEGImage(const fs::path& filename,
|
||||||
|
int width, int height,
|
||||||
|
int rowStride,
|
||||||
|
unsigned char* pixels,
|
||||||
|
bool stripAlpha = false);
|
||||||
|
bool SavePNGImage(const fs::path& filename,
|
||||||
|
int width, int height,
|
||||||
|
int rowStride,
|
||||||
|
unsigned char* pixels,
|
||||||
|
bool stripAlpha = false);
|
|
@ -0,0 +1,240 @@
|
||||||
|
// jpeg.cpp
|
||||||
|
//
|
||||||
|
// Copyright (C) 2001-present, the Celestia Development Team
|
||||||
|
// Original version by Chris Laurel <claurel@shatters.net>
|
||||||
|
//
|
||||||
|
// This program is free software; you can redistribute it and/or
|
||||||
|
// modify it under the terms of the GNU General Public License
|
||||||
|
// as published by the Free Software Foundation; either version 2
|
||||||
|
// of the License, or (at your option) any later version.
|
||||||
|
|
||||||
|
#include <cstdio> // fopen, fclose
|
||||||
|
#include <cstring> // memcpy
|
||||||
|
extern "C" {
|
||||||
|
#include <jpeglib.h>
|
||||||
|
}
|
||||||
|
#include <celengine/glsupport.h>
|
||||||
|
#include <celengine/image.h>
|
||||||
|
#include <celutil/debug.h>
|
||||||
|
|
||||||
|
namespace
|
||||||
|
{
|
||||||
|
struct my_error_mgr
|
||||||
|
{
|
||||||
|
struct jpeg_error_mgr pub; // "public" fields
|
||||||
|
jmp_buf setjmp_buffer; // for return to caller
|
||||||
|
};
|
||||||
|
|
||||||
|
using my_error_ptr = struct my_error_mgr *;
|
||||||
|
|
||||||
|
METHODDEF(void) my_error_exit(j_common_ptr cinfo)
|
||||||
|
{
|
||||||
|
// cinfo->err really points to a my_error_mgr struct, so coerce pointer
|
||||||
|
auto myerr = (my_error_ptr) cinfo->err;
|
||||||
|
|
||||||
|
// Always display the message.
|
||||||
|
// We could postpone this until after returning, if we chose.
|
||||||
|
(*cinfo->err->output_message) (cinfo);
|
||||||
|
|
||||||
|
// Return control to the setjmp point
|
||||||
|
longjmp(myerr->setjmp_buffer, 1);
|
||||||
|
}
|
||||||
|
} // anonymous namespace
|
||||||
|
|
||||||
|
Image* LoadJPEGImage(const fs::path& filename, int /*unused*/)
|
||||||
|
{
|
||||||
|
Image* img = nullptr;
|
||||||
|
|
||||||
|
// This struct contains the JPEG decompression parameters and pointers to
|
||||||
|
// working space (which is allocated as needed by the JPEG library).
|
||||||
|
struct jpeg_decompress_struct cinfo;
|
||||||
|
|
||||||
|
// We use our private extension JPEG error handler.
|
||||||
|
// Note that this struct must live as long as the main JPEG parameter
|
||||||
|
// struct, to avoid dangling-pointer problems.
|
||||||
|
struct my_error_mgr jerr;
|
||||||
|
// More stuff
|
||||||
|
JSAMPARRAY buffer; // Output row buffer
|
||||||
|
int row_stride; // physical row width in output buffer
|
||||||
|
long cont;
|
||||||
|
|
||||||
|
// In this example we want to open the input file before doing anything else,
|
||||||
|
// so that the setjmp() error recovery below can assume the file is open.
|
||||||
|
// VERY IMPORTANT: use "b" option to fopen() if you are on a machine that
|
||||||
|
// requires it in order to read binary files.
|
||||||
|
|
||||||
|
FILE *in;
|
||||||
|
#ifdef _WIN32
|
||||||
|
in = _wfopen(filename.c_str(), L"rb");
|
||||||
|
#else
|
||||||
|
in = fopen(filename.c_str(), "rb");
|
||||||
|
#endif
|
||||||
|
if (!in)
|
||||||
|
return nullptr;
|
||||||
|
|
||||||
|
// Step 1: allocate and initialize JPEG decompression object
|
||||||
|
// We set up the normal JPEG error routines, then override error_exit.
|
||||||
|
cinfo.err = jpeg_std_error(&jerr.pub);
|
||||||
|
jerr.pub.error_exit = my_error_exit;
|
||||||
|
// Establish the setjmp return context for my_error_exit to use.
|
||||||
|
if (setjmp(jerr.setjmp_buffer))
|
||||||
|
{
|
||||||
|
// If we get here, the JPEG code has signaled an error.
|
||||||
|
// We need to clean up the JPEG object, close the input file, and return.
|
||||||
|
jpeg_destroy_decompress(&cinfo);
|
||||||
|
fclose(in);
|
||||||
|
delete img;
|
||||||
|
|
||||||
|
return nullptr;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Now we can initialize the JPEG decompression object.
|
||||||
|
jpeg_create_decompress(&cinfo);
|
||||||
|
|
||||||
|
// Step 2: specify data source (eg, a file)
|
||||||
|
jpeg_stdio_src(&cinfo, in);
|
||||||
|
|
||||||
|
// Step 3: read file parameters with jpeg_read_header()
|
||||||
|
(void) jpeg_read_header(&cinfo, TRUE);
|
||||||
|
|
||||||
|
// We can ignore the return value from jpeg_read_header since
|
||||||
|
// (a) suspension is not possible with the stdio data source, and
|
||||||
|
// (b) we passed TRUE to reject a tables-only JPEG file as an error.
|
||||||
|
|
||||||
|
// Step 4: set parameters for decompression
|
||||||
|
|
||||||
|
// In this example, we don't need to change any of the defaults set by
|
||||||
|
// jpeg_read_header(), so we do nothing here.
|
||||||
|
|
||||||
|
// Step 5: Start decompressor
|
||||||
|
|
||||||
|
(void) jpeg_start_decompress(&cinfo);
|
||||||
|
// We can ignore the return value since suspension is not possible
|
||||||
|
// with the stdio data source.
|
||||||
|
|
||||||
|
// We may need to do some setup of our own at this point before reading
|
||||||
|
// the data. After jpeg_start_decompress() we have the correct scaled
|
||||||
|
// output image dimensions available, as well as the output colormap
|
||||||
|
// if we asked for color quantization.
|
||||||
|
// In this example, we need to make an output work buffer of the right size.
|
||||||
|
// JSAMPLEs per row in output buffer
|
||||||
|
row_stride = cinfo.output_width * cinfo.output_components;
|
||||||
|
// Make a one-row-high sample array that will go away when done with image
|
||||||
|
buffer = (*cinfo.mem->alloc_sarray)
|
||||||
|
((j_common_ptr) & cinfo, JPOOL_IMAGE, row_stride, 1);
|
||||||
|
|
||||||
|
// Step 6: while (scan lines remain to be read)
|
||||||
|
// jpeg_read_scanlines(...);
|
||||||
|
|
||||||
|
// Here we use the library's state variable cinfo.output_scanline as the
|
||||||
|
// loop counter, so that we don't have to keep track ourselves.
|
||||||
|
|
||||||
|
int format = GL_RGB;
|
||||||
|
if (cinfo.output_components == 1)
|
||||||
|
format = GL_LUMINANCE;
|
||||||
|
|
||||||
|
img = new Image(format, cinfo.image_width, cinfo.image_height);
|
||||||
|
|
||||||
|
// cont = cinfo.output_height - 1;
|
||||||
|
cont = 0;
|
||||||
|
while (cinfo.output_scanline < cinfo.output_height)
|
||||||
|
{
|
||||||
|
// jpeg_read_scanlines expects an array of pointers to scanlines.
|
||||||
|
// Here the array is only one element long, but you could ask for
|
||||||
|
// more than one scanline at a time if that's more convenient.
|
||||||
|
(void) jpeg_read_scanlines(&cinfo, buffer, 1);
|
||||||
|
|
||||||
|
// Assume put_scanline_someplace wants a pointer and sample count.
|
||||||
|
// put_scanline_someplace(buffer[0], row_stride);
|
||||||
|
memcpy(img->getPixelRow(cont), buffer[0], row_stride);
|
||||||
|
cont++;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Step 7: Finish decompression
|
||||||
|
|
||||||
|
(void) jpeg_finish_decompress(&cinfo);
|
||||||
|
// We can ignore the return value since suspension is not possible
|
||||||
|
// with the stdio data source.
|
||||||
|
|
||||||
|
// Step 8: Release JPEG decompression object
|
||||||
|
|
||||||
|
// This is an important step since it will release a good deal of memory.
|
||||||
|
jpeg_destroy_decompress(&cinfo);
|
||||||
|
|
||||||
|
// After finish_decompress, we can close the input file.
|
||||||
|
// Here we postpone it until after no more JPEG errors are possible,
|
||||||
|
// so as to simplify the setjmp error logic above. (Actually, I don't
|
||||||
|
// think that jpeg_destroy can do an error exit, but why assume anything...
|
||||||
|
|
||||||
|
fclose(in);
|
||||||
|
|
||||||
|
// At this point you may want to check to see whether any corrupt-data
|
||||||
|
// warnings occurred (test whether jerr.pub.num_warnings is nonzero).
|
||||||
|
|
||||||
|
return img;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool SaveJPEGImage(const fs::path& filename,
|
||||||
|
int width, int height,
|
||||||
|
int rowStride,
|
||||||
|
unsigned char *pixels,
|
||||||
|
bool removeAlpha)
|
||||||
|
{
|
||||||
|
FILE* out;
|
||||||
|
#ifdef _WIN32
|
||||||
|
out = _wfopen(filename.c_str(), L"wb");
|
||||||
|
#else
|
||||||
|
out = fopen(filename.c_str(), "wb");
|
||||||
|
#endif
|
||||||
|
if (out == nullptr)
|
||||||
|
{
|
||||||
|
DPRINTF(LOG_LEVEL_ERROR, "Can't open screen capture file '%s'\n", filename);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
struct jpeg_compress_struct cinfo;
|
||||||
|
|
||||||
|
struct jpeg_error_mgr jerr;
|
||||||
|
JSAMPROW row[1];
|
||||||
|
|
||||||
|
cinfo.err = jpeg_std_error(&jerr);
|
||||||
|
jpeg_create_compress(&cinfo);
|
||||||
|
|
||||||
|
jpeg_stdio_dest(&cinfo, out);
|
||||||
|
|
||||||
|
cinfo.image_width = width;
|
||||||
|
cinfo.image_height = height;
|
||||||
|
cinfo.input_components = 3;
|
||||||
|
cinfo.in_color_space = JCS_RGB;
|
||||||
|
|
||||||
|
jpeg_set_defaults(&cinfo);
|
||||||
|
|
||||||
|
jpeg_set_quality(&cinfo, 90, TRUE);
|
||||||
|
|
||||||
|
jpeg_start_compress(&cinfo, TRUE);
|
||||||
|
|
||||||
|
while (cinfo.next_scanline < cinfo.image_height)
|
||||||
|
{
|
||||||
|
unsigned char *rowHead = &pixels[rowStride * (cinfo.image_height - cinfo.next_scanline - 1)];
|
||||||
|
// Strip alpha values if we are in RGBA format
|
||||||
|
if (removeAlpha)
|
||||||
|
{
|
||||||
|
for (int x = 0; x < width; x++)
|
||||||
|
{
|
||||||
|
const unsigned char* pixelIn = &rowHead[x * 4];
|
||||||
|
unsigned char* pixelOut = &rowHead[x * 3];
|
||||||
|
pixelOut[0] = pixelIn[0];
|
||||||
|
pixelOut[1] = pixelIn[1];
|
||||||
|
pixelOut[2] = pixelIn[2];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
row[0] = rowHead;
|
||||||
|
(void) jpeg_write_scanlines(&cinfo, row, 1);
|
||||||
|
}
|
||||||
|
|
||||||
|
jpeg_finish_compress(&cinfo);
|
||||||
|
fclose(out);
|
||||||
|
jpeg_destroy_compress(&cinfo);
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
|
@ -0,0 +1,258 @@
|
||||||
|
// png.cpp
|
||||||
|
//
|
||||||
|
// Copyright (C) 2001-present, the Celestia Development Team
|
||||||
|
// Original version by Chris Laurel <claurel@shatters.net>
|
||||||
|
//
|
||||||
|
// This program is free software; you can redistribute it and/or
|
||||||
|
// modify it under the terms of the GNU General Public License
|
||||||
|
// as published by the Free Software Foundation; either version 2
|
||||||
|
// of the License, or (at your option) any later version.
|
||||||
|
|
||||||
|
#include <iostream>
|
||||||
|
#include <png.h>
|
||||||
|
#include <zlib.h>
|
||||||
|
#include <fmt/printf.h>
|
||||||
|
#include <celengine/glsupport.h>
|
||||||
|
#include <celengine/image.h>
|
||||||
|
#include <celutil/debug.h>
|
||||||
|
#include <celutil/gettext.h>
|
||||||
|
|
||||||
|
using std::cerr;
|
||||||
|
using std::clog;
|
||||||
|
|
||||||
|
namespace
|
||||||
|
{
|
||||||
|
void PNGReadData(png_structp png_ptr, png_bytep data, png_size_t length)
|
||||||
|
{
|
||||||
|
auto* fp = (FILE*) png_get_io_ptr(png_ptr);
|
||||||
|
if (fread((void*) data, 1, length, fp) != length)
|
||||||
|
cerr << "Error reading PNG data";
|
||||||
|
}
|
||||||
|
|
||||||
|
void PNGWriteData(png_structp png_ptr, png_bytep data, png_size_t length)
|
||||||
|
{
|
||||||
|
auto* fp = (FILE*) png_get_io_ptr(png_ptr);
|
||||||
|
fwrite((void*) data, 1, length, fp);
|
||||||
|
}
|
||||||
|
} // anonymous namespace
|
||||||
|
|
||||||
|
Image* LoadPNGImage(const fs::path& filename)
|
||||||
|
{
|
||||||
|
char header[8];
|
||||||
|
png_structp png_ptr;
|
||||||
|
png_infop info_ptr;
|
||||||
|
png_uint_32 width, height;
|
||||||
|
int bit_depth, color_type, interlace_type;
|
||||||
|
Image* img = nullptr;
|
||||||
|
png_bytep* row_pointers = nullptr;
|
||||||
|
|
||||||
|
#ifdef _WIN32
|
||||||
|
FILE *fp = _wfopen(filename.c_str(), L"rb");
|
||||||
|
#else
|
||||||
|
FILE *fp = fopen(filename.c_str(), "rb");
|
||||||
|
#endif
|
||||||
|
if (fp == nullptr)
|
||||||
|
{
|
||||||
|
fmt::fprintf(clog, _("Error opening image file %s\n"), filename);
|
||||||
|
return nullptr;
|
||||||
|
}
|
||||||
|
|
||||||
|
size_t elements_read;
|
||||||
|
elements_read = fread(header, 1, sizeof(header), fp);
|
||||||
|
if (elements_read == 0 || png_sig_cmp((unsigned char*) header, 0, sizeof(header)))
|
||||||
|
{
|
||||||
|
fmt::fprintf(clog, _("Error: %s is not a PNG file.\n"), filename);
|
||||||
|
fclose(fp);
|
||||||
|
return nullptr;
|
||||||
|
}
|
||||||
|
|
||||||
|
png_ptr = png_create_read_struct(PNG_LIBPNG_VER_STRING,
|
||||||
|
nullptr, nullptr, nullptr);
|
||||||
|
if (png_ptr == nullptr)
|
||||||
|
{
|
||||||
|
fclose(fp);
|
||||||
|
return nullptr;
|
||||||
|
}
|
||||||
|
|
||||||
|
info_ptr = png_create_info_struct(png_ptr);
|
||||||
|
if (info_ptr == nullptr)
|
||||||
|
{
|
||||||
|
fclose(fp);
|
||||||
|
png_destroy_read_struct(&png_ptr, (png_infopp) nullptr, (png_infopp) nullptr);
|
||||||
|
return nullptr;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (setjmp(png_jmpbuf(png_ptr)))
|
||||||
|
{
|
||||||
|
fclose(fp);
|
||||||
|
delete img;
|
||||||
|
png_destroy_read_struct(&png_ptr, &info_ptr, (png_infopp) nullptr);
|
||||||
|
fmt::fprintf(clog, _("Error reading PNG image file %s\n"), filename);
|
||||||
|
return nullptr;
|
||||||
|
}
|
||||||
|
|
||||||
|
// png_init_io(png_ptr, fp);
|
||||||
|
png_set_read_fn(png_ptr, (void*) fp, PNGReadData);
|
||||||
|
png_set_sig_bytes(png_ptr, sizeof(header));
|
||||||
|
|
||||||
|
png_read_info(png_ptr, info_ptr);
|
||||||
|
|
||||||
|
png_get_IHDR(png_ptr, info_ptr,
|
||||||
|
&width, &height, &bit_depth,
|
||||||
|
&color_type, &interlace_type,
|
||||||
|
nullptr, nullptr);
|
||||||
|
|
||||||
|
GLenum glformat = GL_RGB;
|
||||||
|
switch (color_type)
|
||||||
|
{
|
||||||
|
case PNG_COLOR_TYPE_GRAY:
|
||||||
|
glformat = GL_LUMINANCE;
|
||||||
|
break;
|
||||||
|
case PNG_COLOR_TYPE_GRAY_ALPHA:
|
||||||
|
glformat = GL_LUMINANCE_ALPHA;
|
||||||
|
break;
|
||||||
|
case PNG_COLOR_TYPE_RGB:
|
||||||
|
glformat = GL_RGB;
|
||||||
|
break;
|
||||||
|
case PNG_COLOR_TYPE_PALETTE:
|
||||||
|
case PNG_COLOR_TYPE_RGB_ALPHA:
|
||||||
|
glformat = GL_RGBA;
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
// badness
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
img = new Image(glformat, width, height);
|
||||||
|
|
||||||
|
// TODO: consider using paletted textures if they're available
|
||||||
|
if (color_type == PNG_COLOR_TYPE_PALETTE)
|
||||||
|
{
|
||||||
|
png_set_palette_to_rgb(png_ptr);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (color_type == PNG_COLOR_TYPE_GRAY && bit_depth < 8)
|
||||||
|
{
|
||||||
|
png_set_expand_gray_1_2_4_to_8(png_ptr);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (png_get_valid(png_ptr, info_ptr, PNG_INFO_tRNS))
|
||||||
|
{
|
||||||
|
png_set_tRNS_to_alpha(png_ptr);
|
||||||
|
}
|
||||||
|
|
||||||
|
// TODO: consider passing images with < 8 bits/component to
|
||||||
|
// GL without expanding
|
||||||
|
if (bit_depth == 16)
|
||||||
|
png_set_strip_16(png_ptr);
|
||||||
|
else if (bit_depth < 8)
|
||||||
|
png_set_packing(png_ptr);
|
||||||
|
|
||||||
|
row_pointers = new png_bytep[height];
|
||||||
|
for (unsigned int i = 0; i < height; i++)
|
||||||
|
row_pointers[i] = (png_bytep) img->getPixelRow(i);
|
||||||
|
|
||||||
|
png_read_image(png_ptr, row_pointers);
|
||||||
|
|
||||||
|
delete[] row_pointers;
|
||||||
|
|
||||||
|
png_read_end(png_ptr, nullptr);
|
||||||
|
png_destroy_read_struct(&png_ptr, &info_ptr, nullptr);
|
||||||
|
|
||||||
|
fclose(fp);
|
||||||
|
|
||||||
|
return img;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool SavePNGImage(const fs::path& filename,
|
||||||
|
int width, int height,
|
||||||
|
int rowStride,
|
||||||
|
unsigned char *pixels,
|
||||||
|
bool removeAlpha)
|
||||||
|
{
|
||||||
|
#ifdef _WIN32
|
||||||
|
FILE* out = _wfopen(filename.c_str(), L"wb");
|
||||||
|
#else
|
||||||
|
FILE* out = fopen(filename.c_str(), "wb");
|
||||||
|
#endif
|
||||||
|
if (out == nullptr)
|
||||||
|
{
|
||||||
|
DPRINTF(LOG_LEVEL_ERROR, "Can't open screen capture file '%s'\n", filename);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
auto* row_pointers = new png_bytep[height];
|
||||||
|
for (int i = 0; i < height; i++)
|
||||||
|
{
|
||||||
|
unsigned char *rowHead = &pixels[rowStride * (height - i - 1)];
|
||||||
|
// Strip alpha values if we are in RGBA format
|
||||||
|
if (removeAlpha)
|
||||||
|
{
|
||||||
|
for (int x = 0; x < width; x++)
|
||||||
|
{
|
||||||
|
const unsigned char* pixelIn = &rowHead[x * 4];
|
||||||
|
unsigned char* pixelOut = &rowHead[x * 3];
|
||||||
|
pixelOut[0] = pixelIn[0];
|
||||||
|
pixelOut[1] = pixelIn[1];
|
||||||
|
pixelOut[2] = pixelIn[2];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
row_pointers[i] = (png_bytep) rowHead;
|
||||||
|
}
|
||||||
|
|
||||||
|
png_structp png_ptr;
|
||||||
|
png_infop info_ptr;
|
||||||
|
|
||||||
|
png_ptr = png_create_write_struct(PNG_LIBPNG_VER_STRING,
|
||||||
|
nullptr, nullptr, nullptr);
|
||||||
|
|
||||||
|
if (png_ptr == nullptr)
|
||||||
|
{
|
||||||
|
DPRINTF(LOG_LEVEL_ERROR, "Screen capture: error allocating png_ptr\n");
|
||||||
|
fclose(out);
|
||||||
|
delete[] row_pointers;
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
info_ptr = png_create_info_struct(png_ptr);
|
||||||
|
if (info_ptr == nullptr)
|
||||||
|
{
|
||||||
|
DPRINTF(LOG_LEVEL_ERROR, "Screen capture: error allocating info_ptr\n");
|
||||||
|
fclose(out);
|
||||||
|
delete[] row_pointers;
|
||||||
|
png_destroy_write_struct(&png_ptr, (png_infopp) nullptr);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (setjmp(png_jmpbuf(png_ptr)))
|
||||||
|
{
|
||||||
|
DPRINTF(LOG_LEVEL_ERROR, "Error writing PNG file '%s'\n", filename);
|
||||||
|
fclose(out);
|
||||||
|
delete[] row_pointers;
|
||||||
|
png_destroy_write_struct(&png_ptr, &info_ptr);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
// png_init_io(png_ptr, out);
|
||||||
|
png_set_write_fn(png_ptr, (void*) out, PNGWriteData, nullptr);
|
||||||
|
|
||||||
|
png_set_compression_level(png_ptr, Z_BEST_COMPRESSION);
|
||||||
|
png_set_IHDR(png_ptr, info_ptr,
|
||||||
|
width, height,
|
||||||
|
8,
|
||||||
|
PNG_COLOR_TYPE_RGB,
|
||||||
|
PNG_INTERLACE_NONE,
|
||||||
|
PNG_COMPRESSION_TYPE_DEFAULT,
|
||||||
|
PNG_FILTER_TYPE_DEFAULT);
|
||||||
|
|
||||||
|
png_write_info(png_ptr, info_ptr);
|
||||||
|
png_write_image(png_ptr, row_pointers);
|
||||||
|
png_write_end(png_ptr, info_ptr);
|
||||||
|
|
||||||
|
// Clean up everything . . .
|
||||||
|
png_destroy_write_struct(&png_ptr, &info_ptr);
|
||||||
|
delete[] row_pointers;
|
||||||
|
fclose(out);
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
Loading…
Reference in New Issue