Refactor Image class and image capture

* use GL_MESA_pack_invert to flip captured buffer
 * move enum PixelFormat to own file
 * use PixelFormat instead of GL formats in Image
 * provide captureImage() in CelestiaCore
 * use CelestiaCore methods to capture images in Qt UI
pull/1094/head
Hleb Valoshka 2021-06-13 14:48:54 +04:00
parent c7018259a6
commit bece12f6e4
25 changed files with 283 additions and 255 deletions

View File

@ -340,7 +340,7 @@ void Galaxy::renderGalaxyPointSprites(const Vector3f& offset,
if (galaxyTex == nullptr) if (galaxyTex == nullptr)
{ {
galaxyTex = CreateProceduralTexture(width, height, GL_RGBA, galaxyTex = CreateProceduralTexture(width, height, PixelFormat::RGBA,
GalaxyTextureEval); GalaxyTextureEval);
} }
assert(galaxyTex != nullptr); assert(galaxyTex != nullptr);
@ -349,7 +349,7 @@ void Galaxy::renderGalaxyPointSprites(const Vector3f& offset,
if (colorTex == nullptr) if (colorTex == nullptr)
{ {
colorTex = CreateProceduralTexture(256, 1, GL_RGBA, colorTex = CreateProceduralTexture(256, 1, PixelFormat::RGBA,
ColorTextureEval, ColorTextureEval,
Texture::EdgeClamp, Texture::EdgeClamp,
Texture::NoMipMaps); Texture::NoMipMaps);

View File

@ -430,14 +430,16 @@ void Globular::renderGlobularPointSprites(
if(centerTex[ic] == nullptr) if(centerTex[ic] == nullptr)
{ {
centerTex[ic] = CreateProceduralTexture(cntrTexWidth, cntrTexHeight, GL_RGBA, centerTex[ic] = CreateProceduralTexture(cntrTexWidth, cntrTexHeight,
PixelFormat::RGBA,
CenterCloudTexEval); CenterCloudTexEval);
} }
assert(centerTex[ic] != nullptr); assert(centerTex[ic] != nullptr);
if (globularTex == nullptr) if (globularTex == nullptr)
{ {
globularTex = CreateProceduralTexture(starTexWidth, starTexHeight, GL_RGBA, globularTex = CreateProceduralTexture(starTexWidth, starTexHeight,
PixelFormat::RGBA,
GlobularTextureEval); GlobularTextureEval);
} }
assert(globularTex != nullptr); assert(globularTex != nullptr);

View File

@ -15,6 +15,7 @@ bool EXT_framebuffer_object = false;
bool ARB_shader_texture_lod = false; bool ARB_shader_texture_lod = false;
bool EXT_texture_compression_s3tc = false; bool EXT_texture_compression_s3tc = false;
bool EXT_texture_filter_anisotropic = false; bool EXT_texture_filter_anisotropic = false;
bool MESA_pack_invert = false;
GLint maxPointSize = 0; GLint maxPointSize = 0;
GLfloat maxLineWidth = 0.0f; GLfloat maxLineWidth = 0.0f;
@ -42,6 +43,7 @@ bool init(util::array_view<std::string> ignore) noexcept
ARB_shader_texture_lod = check_extension(ignore, "GL_ARB_shader_texture_lod"); ARB_shader_texture_lod = check_extension(ignore, "GL_ARB_shader_texture_lod");
EXT_texture_compression_s3tc = check_extension(ignore, "GL_EXT_texture_compression_s3tc"); EXT_texture_compression_s3tc = check_extension(ignore, "GL_EXT_texture_compression_s3tc");
EXT_texture_filter_anisotropic = check_extension(ignore, "GL_EXT_texture_filter_anisotropic"); EXT_texture_filter_anisotropic = check_extension(ignore, "GL_EXT_texture_filter_anisotropic");
MESA_pack_invert = check_extension(ignore, "GL_MESA_pack_invert");
GLint pointSizeRange[2]; GLint pointSizeRange[2];
GLfloat lineWidthRange[2]; GLfloat lineWidthRange[2];

View File

@ -40,6 +40,7 @@ constexpr const int GLES_2 = 20;
extern bool ARB_shader_texture_lod; extern bool ARB_shader_texture_lod;
extern bool EXT_texture_compression_s3tc; extern bool EXT_texture_compression_s3tc;
extern bool EXT_texture_filter_anisotropic; extern bool EXT_texture_filter_anisotropic;
extern bool MESA_pack_invert;
#ifdef GL_ES #ifdef GL_ES
extern bool OES_vertex_array_object; extern bool OES_vertex_array_object;
#else #else

View File

@ -19,6 +19,7 @@
#include "image.h" #include "image.h"
using namespace std; using namespace std;
using celestia::PixelFormat;
namespace namespace
{ {
@ -28,32 +29,27 @@ int pad(int n)
return (n + 3) & ~0x3; return (n + 3) & ~0x3;
} }
int formatComponents(int fmt) int formatComponents(PixelFormat fmt)
{ {
switch (fmt) switch (fmt)
{ {
case GL_RGBA: case PixelFormat::RGBA:
case GL_BGRA_EXT: case PixelFormat::BGRA:
return 4; return 4;
case GL_RGB: case PixelFormat::RGB:
#ifndef GL_ES case PixelFormat::BGR:
case GL_BGR_EXT:
#endif
return 3; return 3;
case GL_LUMINANCE_ALPHA: case PixelFormat::LUM_ALPHA:
#ifndef GL_ES
case GL_DSDT_NV:
#endif
return 2; return 2;
case GL_ALPHA: case PixelFormat::ALPHA:
case GL_LUMINANCE: case PixelFormat::LUMINANCE:
return 1; return 1;
// Compressed formats // Compressed formats
case GL_COMPRESSED_RGBA_S3TC_DXT1_EXT: case PixelFormat::DXT1:
return 3; return 3;
case GL_COMPRESSED_RGBA_S3TC_DXT3_EXT: case PixelFormat::DXT3:
case GL_COMPRESSED_RGBA_S3TC_DXT5_EXT: case PixelFormat::DXT5:
return 4; return 4;
// Unknown format // Unknown format
@ -62,18 +58,18 @@ int formatComponents(int fmt)
} }
} }
int calcMipLevelSize(int fmt, int w, int h, int mip) int calcMipLevelSize(PixelFormat 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);
switch (fmt) switch (fmt)
{ {
case GL_COMPRESSED_RGBA_S3TC_DXT1_EXT: case PixelFormat::DXT1:
// 4x4 blocks, 8 bytes per block // 4x4 blocks, 8 bytes per block
return ((w + 3) / 4) * ((h + 3) / 4) * 8; return ((w + 3) / 4) * ((h + 3) / 4) * 8;
case GL_COMPRESSED_RGBA_S3TC_DXT3_EXT: case PixelFormat::DXT3:
case GL_COMPRESSED_RGBA_S3TC_DXT5_EXT: case PixelFormat::DXT5:
// 4x4 blocks, 16 bytes per block // 4x4 blocks, 16 bytes per block
return ((w + 3) / 4) * ((h + 3) / 4) * 16; return ((w + 3) / 4) * ((h + 3) / 4) * 16;
default: default:
@ -82,7 +78,7 @@ int calcMipLevelSize(int fmt, int w, int h, int mip)
} }
} // anonymous namespace } // anonymous namespace
Image::Image(int fmt, int w, int h, int mip) : Image::Image(PixelFormat fmt, int w, int h, int mip) :
width(w), width(w),
height(h), height(h),
mipLevels(mip), mipLevels(mip),
@ -96,12 +92,12 @@ Image::Image(int fmt, int w, int h, int mip) :
size = 1; size = 1;
for (int i = 0; i < mipLevels; i++) for (int i = 0; i < mipLevels; i++)
size += calcMipLevelSize(fmt, w, h, i); size += calcMipLevelSize(fmt, w, h, i);
pixels = new unsigned char[size]; pixels = make_unique<uint8_t[]>(size);
} }
Image::~Image() bool Image::isValid() const noexcept
{ {
delete[] pixels; return pixels != nullptr;
} }
int Image::getWidth() const int Image::getWidth() const
@ -129,7 +125,7 @@ int Image::getSize() const
return size; return size;
} }
int Image::getFormat() const PixelFormat Image::getFormat() const
{ {
return format; return format;
} }
@ -139,12 +135,12 @@ int Image::getComponents() const
return components; return components;
} }
unsigned char* Image::getPixels() uint8_t* Image::getPixels()
{ {
return pixels; return pixels.get();
} }
unsigned char* Image::getPixelRow(int mip, int row) uint8_t* Image::getPixelRow(int mip, int row)
{ {
/*int w = max(width >> mip, 1); Unused*/ /*int w = max(width >> mip, 1); Unused*/
int h = max(height >> mip, 1); int h = max(height >> mip, 1);
@ -158,12 +154,12 @@ unsigned char* Image::getPixelRow(int mip, int row)
return getMipLevel(mip) + row * pitch; return getMipLevel(mip) + row * pitch;
} }
unsigned char* Image::getPixelRow(int row) uint8_t* Image::getPixelRow(int row)
{ {
return getPixelRow(0, row); return getPixelRow(0, row);
} }
unsigned char* Image::getMipLevel(int mip) uint8_t* Image::getMipLevel(int mip)
{ {
if (mip >= mipLevels) if (mip >= mipLevels)
return nullptr; return nullptr;
@ -172,7 +168,7 @@ unsigned char* Image::getMipLevel(int mip)
for (int i = 0; i < mip; i++) for (int i = 0; i < mip; i++)
offset += calcMipLevelSize(format, width, height, i); offset += calcMipLevelSize(format, width, height, i);
return pixels + offset; return pixels.get() + offset;
} }
int Image::getMipLevelSize(int mip) const int Image::getMipLevelSize(int mip) const
@ -187,9 +183,9 @@ bool Image::isCompressed() const
{ {
switch (format) switch (format)
{ {
case GL_COMPRESSED_RGBA_S3TC_DXT1_EXT: case PixelFormat::DXT1:
case GL_COMPRESSED_RGBA_S3TC_DXT3_EXT: case PixelFormat::DXT3:
case GL_COMPRESSED_RGBA_S3TC_DXT5_EXT: case PixelFormat::DXT5:
return true; return true;
default: default:
return false; return false;
@ -200,12 +196,12 @@ bool Image::hasAlpha() const
{ {
switch (format) switch (format)
{ {
case GL_COMPRESSED_RGBA_S3TC_DXT3_EXT: case PixelFormat::DXT3:
case GL_COMPRESSED_RGBA_S3TC_DXT5_EXT: case PixelFormat::DXT5:
case GL_RGBA: case PixelFormat::RGBA:
case GL_BGRA_EXT: case PixelFormat::BGRA:
case GL_LUMINANCE_ALPHA: case PixelFormat::LUM_ALPHA:
case GL_ALPHA: case PixelFormat::ALPHA:
return true; return true;
default: default:
return false; return false;
@ -225,9 +221,9 @@ Image* Image::computeNormalMap(float scale, bool wrap) const
if (isCompressed()) if (isCompressed())
return nullptr; return nullptr;
auto* normalMap = new Image(GL_RGBA, width, height); auto* normalMap = new Image(PixelFormat::RGBA, width, height);
unsigned char* nmPixels = normalMap->getPixels(); uint8_t* nmPixels = normalMap->getPixels();
int nmPitch = normalMap->getPitch(); int nmPitch = normalMap->getPitch();
// Compute normals using differences between adjacent texels. // Compute normals using differences between adjacent texels.
@ -275,9 +271,9 @@ Image* Image::computeNormalMap(float scale, bool wrap) const
float rmag = 1.0f / mag; float rmag = 1.0f / mag;
int n = i * nmPitch + j * 4; int n = i * nmPitch + j * 4;
nmPixels[n] = (unsigned char) (128 + 127 * dx * rmag); nmPixels[n] = (uint8_t) (128 + 127 * dx * rmag);
nmPixels[n + 1] = (unsigned char) (128 + 127 * dy * rmag); nmPixels[n + 1] = (uint8_t) (128 + 127 * dy * rmag);
nmPixels[n + 2] = (unsigned char) (128 + 127 * rmag); nmPixels[n + 2] = (uint8_t) (128 + 127 * rmag);
nmPixels[n + 3] = 255; nmPixels[n + 3] = 255;
} }
} }

View File

@ -10,7 +10,9 @@
#pragma once #pragma once
#include <memory>
#include <celcompat/filesystem.h> #include <celcompat/filesystem.h>
#include <celengine/pixelformat.h>
// The image class supports multiple GL formats, including compressed ones. // The image class supports multiple GL formats, including compressed ones.
// Mipmaps may be stored within an image as well. The mipmaps are stored in // Mipmaps may be stored within an image as well. The mipmaps are stored in
@ -21,19 +23,24 @@
class Image class Image
{ {
public: public:
Image(int fmt, int w, int h, int mip = 1); Image(celestia::PixelFormat format, int w, int h, int mip = 1);
~Image(); ~Image() = default;
Image(Image&&) = default;
Image(const Image&) = delete;
Image& operator=(Image&&) = default;
Image& operator=(const Image&) = delete;
bool isValid() const noexcept;
int getWidth() const; int getWidth() const;
int getHeight() const; int getHeight() const;
int getPitch() const; int getPitch() const;
int getMipLevelCount() const; int getMipLevelCount() const;
int getFormat() const; celestia::PixelFormat getFormat() const;
int getComponents() const; int getComponents() const;
unsigned char* getPixels(); uint8_t* getPixels();
unsigned char* getPixelRow(int row); uint8_t* getPixelRow(int row);
unsigned char* getPixelRow(int mip, int row); uint8_t* getPixelRow(int mip, int row);
unsigned char* getMipLevel(int mip); uint8_t* getMipLevel(int mip);
int getSize() const; int getSize() const;
int getMipLevelSize(int mip) const; int getMipLevelSize(int mip) const;
@ -54,9 +61,9 @@ class Image
int pitch; int pitch;
int mipLevels; int mipLevels;
int components; int components;
int format; celestia::PixelFormat format;
int size; int size;
unsigned char* pixels{ nullptr }; std::unique_ptr<uint8_t[]> pixels;
}; };
Image* LoadImageFromFile(const fs::path& filename); Image* LoadImageFromFile(const fs::path& filename);

View File

@ -0,0 +1,28 @@
// pixelformat.h
//
// Copyright (C) 2021-present, the Celestia Development Team
//
// 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
namespace celestia
{
enum class PixelFormat
{
INVALID = -1,
RGB = 0x1907, // GL_RGB
RGBA = 0x1908, // GL_RGBA
BGR = 0x80E0, // GL_BGR
BGRA = 0x80E1, // GL_BGRA
LUM_ALPHA = 0x190A, // GL_LUMINANCE_ALPHA
ALPHA = 0x1906, // GL_ALPHA
LUMINANCE = 0x1909, // GL_LUMINANCE
DXT1 = 0x83F1, // GL_COMPRESSED_RGBA_S3TC_DXT1_EXT
DXT3 = 0x83F2, // GL_COMPRESSED_RGBA_S3TC_DXT3_EXT
DXT5 = 0x83F3, // GL_COMPRESSED_RGBA_S3TC_DXT5_EXT
};
}

View File

@ -33,7 +33,6 @@ std::ofstream hdrlog;
#define EXPOSURE_HALFLIFE 0.4f #define EXPOSURE_HALFLIFE 0.4f
#endif #endif
#include <config.h>
#include "render.h" #include "render.h"
#include "boundaries.h" #include "boundaries.h"
#include "dsorenderer.h" #include "dsorenderer.h"
@ -85,6 +84,10 @@ std::ofstream hdrlog;
#ifdef USE_GLCONTEXT #ifdef USE_GLCONTEXT
#include "glcontext.h" #include "glcontext.h"
#endif #endif
#ifdef _MSC_VER
#include <malloc.h>
#define alloca(s) _alloca(s)
#endif
using namespace cmod; using namespace cmod;
using namespace Eigen; using namespace Eigen;
@ -462,7 +465,7 @@ static void BuildGlareMipLevel2(unsigned char* mipPixels,
static Texture* BuildGaussianDiscTexture(unsigned int log2size) static Texture* BuildGaussianDiscTexture(unsigned int log2size)
{ {
unsigned int size = 1 << log2size; unsigned int size = 1 << log2size;
Image* img = new Image(GL_LUMINANCE, size, size, log2size + 1); Image* img = new Image(PixelFormat::LUMINANCE, size, size, log2size + 1);
for (unsigned int mipLevel = 0; mipLevel <= log2size; mipLevel++) for (unsigned int mipLevel = 0; mipLevel <= log2size; mipLevel++)
{ {
@ -486,7 +489,7 @@ static Texture* BuildGaussianDiscTexture(unsigned int log2size)
static Texture* BuildGaussianGlareTexture(unsigned int log2size) static Texture* BuildGaussianGlareTexture(unsigned int log2size)
{ {
unsigned int size = 1 << log2size; unsigned int size = 1 << log2size;
Image* img = new Image(GL_LUMINANCE, size, size, log2size + 1); Image* img = new Image(PixelFormat::LUMINANCE, size, size, log2size + 1);
for (unsigned int mipLevel = 0; mipLevel <= log2size; mipLevel++) for (unsigned int mipLevel = 0; mipLevel <= log2size; mipLevel++)
{ {
@ -600,7 +603,7 @@ bool Renderer::init(
} }
#ifdef USE_HDR #ifdef USE_HDR
Image *testImg = new Image(GL_LUMINANCE_ALPHA, 1, 1); Image *testImg = new Image(PixelFormat::LUM_ALPHA, 1, 1);
ImageTexture *testTex = new ImageTexture(*testImg, ImageTexture *testTex = new ImageTexture(*testImg,
Texture::EdgeClamp, Texture::EdgeClamp,
Texture::NoMipMaps); Texture::NoMipMaps);
@ -635,6 +638,9 @@ bool Renderer::init(
glEnable(GL_CULL_FACE); glEnable(GL_CULL_FACE);
glCullFace(GL_BACK); glCullFace(GL_BACK);
if (gl::MESA_pack_invert)
glPixelStorei(GL_PACK_INVERT_MESA, GL_TRUE);
// LEQUAL rather than LESS required for multipass rendering // LEQUAL rather than LESS required for multipass rendering
glDepthFunc(GL_LEQUAL); glDepthFunc(GL_LEQUAL);
@ -5493,19 +5499,54 @@ void Renderer::disableDepthTest() noexcept
} }
} }
constexpr GLenum toGLFormat(Renderer::PixelFormat format) constexpr GLenum toGLFormat(PixelFormat format)
{ {
return (GLenum) format; return (GLenum) format;
} }
bool Renderer::captureFrame(int x, int y, int w, int h, Renderer::PixelFormat format, unsigned char* buffer, bool back) const constexpr int formatWidth(PixelFormat format)
{ {
return format == PixelFormat::RGB
#ifndef GL_ES #ifndef GL_ES
glReadBuffer(back ? GL_BACK : GL_FRONT); || format == PixelFormat::BGR
#endif #endif
glReadPixels(x, y, w, h, toGLFormat(format), GL_UNSIGNED_BYTE, (void*) buffer); ? 3 : 4;
}
return glGetError() == GL_NO_ERROR; PixelFormat
Renderer::getPreferredCaptureFormat() const noexcept
{
#ifdef GL_ES
return PixelFormat::RGBA;
#else
return PixelFormat::RGB;
#endif
}
bool Renderer::captureFrame(int x, int y, int w, int h, PixelFormat format, unsigned char* buffer) const
{
glReadPixels(x, y, w, h, toGLFormat(format), GL_UNSIGNED_BYTE, (void*) buffer);
bool ok = glGetError() == GL_NO_ERROR;
if (!ok)
return false;
if (!gl::MESA_pack_invert)
{
int realWidth = w * formatWidth(format);
#if defined(__GNUC__) && !defined(__STRICT_ANSI__)
uint8_t tempLine[realWidth]; // G++ supports VLA as an extension
#else
uint8_t *tempLine = static_cast<uint8_t*>(alloca(realWidth));
#endif
uint8_t *fb = buffer;
for (int i = 0, p = realWidth * (h - 1); i < p; i += realWidth, p -= realWidth)
{
memcpy(tempLine, &fb[i], realWidth);
memcpy(&fb[i], &fb[p], realWidth);
memcpy(&fb[p], tempLine, realWidth);
}
}
return ok;
} }
void Renderer::drawRectangle(const celestia::Rect &r, int fishEyeOverrideMode, const Eigen::Matrix4f& p, const Eigen::Matrix4f& m) void Renderer::drawRectangle(const celestia::Rect &r, int fishEyeOverrideMode, const Eigen::Matrix4f& p, const Eigen::Matrix4f& m)

View File

@ -252,17 +252,6 @@ class Renderer
StarStyleCount = 3, StarStyleCount = 3,
}; };
// Pixel formats for image and video capture.
// Currently we map them 1:1 to GL
enum class PixelFormat
{
RGBA = GL_RGBA,
RGB = GL_RGB,
#ifndef GL_ES
BGR_EXT = GL_BGR_EXT
#endif
};
uint64_t getRenderFlags() const; uint64_t getRenderFlags() const;
void setRenderFlags(uint64_t); void setRenderFlags(uint64_t);
int getLabelMode() const; int getLabelMode() const;
@ -314,6 +303,8 @@ class Renderer
void enableDepthTest() noexcept; void enableDepthTest() noexcept;
void disableDepthTest() noexcept; void disableDepthTest() noexcept;
celestia::PixelFormat getPreferredCaptureFormat() const noexcept;
void drawRectangle(const celestia::Rect& r, int fishEyeOverrideMode, const Eigen::Matrix4f& p, const Eigen::Matrix4f& m = Eigen::Matrix4f::Identity()); void drawRectangle(const celestia::Rect& r, int fishEyeOverrideMode, const Eigen::Matrix4f& p, const Eigen::Matrix4f& m = Eigen::Matrix4f::Identity());
void setRenderRegion(int x, int y, int width, int height, bool withScissor = true); void setRenderRegion(int x, int y, int width, int height, bool withScissor = true);
@ -324,7 +315,7 @@ class Renderer
void setSolarSystemMaxDistance(float); void setSolarSystemMaxDistance(float);
void setShadowMapSize(unsigned); void setShadowMapSize(unsigned);
bool captureFrame(int, int, int, int, PixelFormat format, unsigned char*, bool = false) const; bool captureFrame(int, int, int, int, celestia::PixelFormat format, unsigned char*) const;
void renderMarker(celestia::MarkerRepresentation::Symbol symbol, void renderMarker(celestia::MarkerRepresentation::Symbol symbol,
float size, float size,

View File

@ -7,7 +7,6 @@
// 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 <cstdlib> #include <cstdlib>
@ -117,40 +116,37 @@ static const TextureCaps& GetTextureCaps()
} }
static int getInternalFormat(PixelFormat format)
static int getInternalFormat(int format)
{ {
#ifdef GL_ES #ifdef GL_ES
switch (format) switch (format)
{ {
case GL_RGBA: case PixelFormat::RGBA:
case GL_RGB: case PixelFormat::RGB:
case GL_LUMINANCE_ALPHA: case PixelFormat::LUM_ALPHA:
case GL_ALPHA: case PixelFormat::ALPHA:
case GL_LUMINANCE: case PixelFormat::LUMINANCE:
case GL_COMPRESSED_RGBA_S3TC_DXT1_EXT: case PixelFormat::DXT1:
case GL_COMPRESSED_RGBA_S3TC_DXT3_EXT: case PixelFormat::DXT3:
case GL_COMPRESSED_RGBA_S3TC_DXT5_EXT: case PixelFormat::DXT5:
return format; return (int) format;
default: default:
return 0; return 0;
} }
#else #else
switch (format) switch (format)
{ {
case GL_RGBA: case PixelFormat::RGBA:
case GL_BGRA: case PixelFormat::BGRA:
case GL_RGB: case PixelFormat::RGB:
case GL_BGR: case PixelFormat::BGR:
case GL_LUMINANCE_ALPHA: case PixelFormat::LUM_ALPHA:
case GL_ALPHA: case PixelFormat::ALPHA:
case GL_INTENSITY: case PixelFormat::LUMINANCE:
case GL_LUMINANCE: case PixelFormat::DXT1:
case GL_DSDT_NV: case PixelFormat::DXT3:
case GL_COMPRESSED_RGBA_S3TC_DXT1_EXT: case PixelFormat::DXT5:
case GL_COMPRESSED_RGBA_S3TC_DXT3_EXT: return (int) format;
case GL_COMPRESSED_RGBA_S3TC_DXT5_EXT:
return format;
default: default:
return 0; return 0;
} }
@ -190,9 +186,9 @@ static int getCompressedInternalFormat(int format)
#endif #endif
static int getCompressedBlockSize(int format) static int getCompressedBlockSize(PixelFormat format)
{ {
return format == GL_COMPRESSED_RGBA_S3TC_DXT1_EXT ? 8 : 16; return format == PixelFormat::DXT1 ? 8 : 16;
} }
@ -738,14 +734,13 @@ const TextureTile TiledTexture::getTile(int lod, int u, int v)
} }
CubeMap::CubeMap(Image* faces[]) : CubeMap::CubeMap(Image* faces[]) :
Texture(faces[0]->getWidth(), faces[0]->getHeight()), Texture(faces[0]->getWidth(), faces[0]->getHeight()),
glName(0) glName(0)
{ {
// Verify that all the faces are square and have the same size // Verify that all the faces are square and have the same size
int width = faces[0]->getWidth(); int width = faces[0]->getWidth();
int format = faces[0]->getFormat(); PixelFormat format = faces[0]->getFormat();
int i = 0; int i = 0;
for (i = 0; i < 6; i++) for (i = 0; i < 6; i++)
{ {
@ -855,7 +850,7 @@ void CubeMap::setBorderColor(Color borderColor)
Texture* CreateProceduralTexture(int width, int height, Texture* CreateProceduralTexture(int width, int height,
int format, PixelFormat format,
ProceduralTexEval func, ProceduralTexEval func,
Texture::AddressMode addressMode, Texture::AddressMode addressMode,
Texture::MipMapMode mipMode) Texture::MipMapMode mipMode)
@ -880,7 +875,7 @@ Texture* CreateProceduralTexture(int width, int height,
Texture* CreateProceduralTexture(int width, int height, Texture* CreateProceduralTexture(int width, int height,
int format, PixelFormat format,
TexelFunctionObject& func, TexelFunctionObject& func,
Texture::AddressMode addressMode, Texture::AddressMode addressMode,
Texture::MipMapMode mipMode) Texture::MipMapMode mipMode)
@ -940,8 +935,9 @@ static Vector3f cubeVector(int face, float s, float t)
} }
extern Texture* CreateProceduralCubeMap(int size, int format, Texture* CreateProceduralCubeMap(int size,
ProceduralTexEval func) PixelFormat format,
ProceduralTexEval func)
{ {
Image* faces[6]; Image* faces[6];
@ -1056,7 +1052,7 @@ Texture* LoadTextureFromFile(const fs::path& filename,
// compressed normal map. There's no separate OpenGL format for dxt5 // compressed normal map. There's no separate OpenGL format for dxt5
// normal maps, so the file extension is the only thing that // normal maps, so the file extension is the only thing that
// distinguishes it from a plain old dxt5 texture. // distinguishes it from a plain old dxt5 texture.
if (img->getFormat() == GL_COMPRESSED_RGBA_S3TC_DXT5_EXT) if (img->getFormat() == PixelFormat::DXT5)
{ {
tex->setFormatOptions(Texture::DXT5NormalMap); tex->setFormatOptions(Texture::DXT5NormalMap);
} }

View File

@ -168,16 +168,16 @@ class CubeMap : public Texture
extern Texture* CreateProceduralTexture(int width, int height, extern Texture* CreateProceduralTexture(int width, int height,
int format, celestia::PixelFormat format,
ProceduralTexEval func, ProceduralTexEval func,
Texture::AddressMode addressMode = Texture::EdgeClamp, Texture::AddressMode addressMode = Texture::EdgeClamp,
Texture::MipMapMode mipMode = Texture::DefaultMipMaps); Texture::MipMapMode mipMode = Texture::DefaultMipMaps);
extern Texture* CreateProceduralTexture(int width, int height, extern Texture* CreateProceduralTexture(int width, int height,
int format, celestia::PixelFormat format,
TexelFunctionObject& func, TexelFunctionObject& func,
Texture::AddressMode addressMode = Texture::EdgeClamp, Texture::AddressMode addressMode = Texture::EdgeClamp,
Texture::MipMapMode mipMode = Texture::DefaultMipMaps); Texture::MipMapMode mipMode = Texture::DefaultMipMaps);
extern Texture* CreateProceduralCubeMap(int size, int format, extern Texture* CreateProceduralCubeMap(int size, celestia::PixelFormat format,
ProceduralTexEval func); ProceduralTexEval func);
extern Texture* LoadTextureFromFile(const fs::path& filename, extern Texture* LoadTextureFromFile(const fs::path& filename,

View File

@ -13,8 +13,6 @@ set(CELESTIA_SOURCES
favorites.h favorites.h
helper.cpp helper.cpp
helper.h helper.h
imagecapture.cpp
imagecapture.h
scriptmenu.cpp scriptmenu.cpp
scriptmenu.h scriptmenu.h
url.cpp url.cpp

View File

@ -31,6 +31,7 @@
#include <celengine/planetgrid.h> #include <celengine/planetgrid.h>
#include <celengine/visibleregion.h> #include <celengine/visibleregion.h>
#include <celengine/framebuffer.h> #include <celengine/framebuffer.h>
#include <celimage/imageformats.h>
#include <celmath/geomutil.h> #include <celmath/geomutil.h>
#include <celutil/color.h> #include <celutil/color.h>
#include <celutil/filetype.h> #include <celutil/filetype.h>
@ -60,12 +61,11 @@
#endif #endif
#include <celttf/truetypefont.h> #include <celttf/truetypefont.h>
#include "imagecapture.h"
using namespace Eigen; using namespace Eigen;
using namespace std; using namespace std;
using namespace astro::literals; using namespace astro::literals;
using namespace celmath; using namespace celmath;
using namespace celestia;
using namespace celestia::scripts; using namespace celestia::scripts;
using namespace celestia::util; using namespace celestia::util;
@ -4719,20 +4719,49 @@ View* CelestiaCore::getViewByObserver(const Observer *obs) const
return nullptr; return nullptr;
} }
Image CelestiaCore::captureImage() const
{
// Get the dimensions of the current viewport
array<int, 4> viewport;
getRenderer()->getViewport(viewport);
PixelFormat format = renderer->getPreferredCaptureFormat();
Image image(format, viewport[2], viewport[3]);
if (!renderer->captureFrame(viewport[0], viewport[1],
viewport[2], viewport[3],
format, image.getPixels()))
{
fmt::print(cerr, _("Unable to capture a frame!\n"));
}
return image;
}
bool CelestiaCore::saveScreenShot(const fs::path& filename, ContentType type) const bool CelestiaCore::saveScreenShot(const fs::path& filename, ContentType type) const
{ {
if (type == Content_Unknown) if (type == Content_Unknown)
type = DetermineFileType(filename); type = DetermineFileType(filename);
// Get the dimensions of the current viewport if (type != Content_JPEG && type != Content_PNG)
array<int, 4> viewport; {
getRenderer()->getViewport(viewport); fmt::print(cerr, _("Unsupported image type: {}!\n"), filename.string());
return false;
}
return CaptureBufferToFile(filename, Image image = captureImage();
viewport[0], viewport[1], if (!image.isValid())
viewport[2], viewport[3], return false;
getRenderer(),
type); switch (type)
{
case Content_JPEG:
return SaveJPEGImage(filename, image);
case Content_PNG:
return SavePNGImage(filename, image);
default:
break;
}
return false;
} }
void CelestiaCore::setLogFile(fs::path &fn) void CelestiaCore::setLogFile(fs::path &fn)

View File

@ -386,6 +386,7 @@ class CelestiaCore // : public Watchable<CelestiaCore>
void setScriptHook(std::unique_ptr<celestia::scripts::IScriptHook> &&hook) { m_scriptHook = std::move(hook); } void setScriptHook(std::unique_ptr<celestia::scripts::IScriptHook> &&hook) { m_scriptHook = std::move(hook); }
const std::shared_ptr<celestia::scripts::ScriptMaps>& scriptMaps() const { return m_scriptMaps; } const std::shared_ptr<celestia::scripts::ScriptMaps>& scriptMaps() const { return m_scriptMaps; }
Image captureImage() const;
bool saveScreenShot(const fs::path&, ContentType = Content_Unknown) const; bool saveScreenShot(const fs::path&, ContentType = Content_Unknown) const;
protected: protected:

View File

@ -1,65 +0,0 @@
// imagecapture.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 <memory>
#include <fmt/format.h>
#include <celengine/render.h>
#include <celimage/imageformats.h>
#include <celutil/gettext.h>
#include "imagecapture.h"
using fmt::print;
using std::cerr;
using std::unique_ptr;
bool CaptureBufferToFile(const fs::path& filename,
int x, int y,
int width, int height,
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
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 = unique_ptr<unsigned char[]>(new unsigned char[imageSize]);
if (!renderer->captureFrame(x, y, width, height,
format, pixels.get(), true))
{
print(cerr, _("Unable to capture a frame!\n"));
return false;
}
bool removeAlpha = format == Renderer::PixelFormat::RGBA;
switch (type)
{
case Content_JPEG:
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;
}

View File

@ -1,22 +0,0 @@
// imagecapture.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 <celcompat/filesystem.h>
#include <celutil/filetype.h>
class Renderer;
bool CaptureBufferToFile(const fs::path& filename,
int x, int y,
int width, int height,
const Renderer *renderer,
ContentType type);

View File

@ -411,21 +411,8 @@ static void captureImage(AVFrame *pict, int width, int height, const Renderer *r
x += (w - width) / 2; x += (w - width) / 2;
y += (h - height) / 2; y += (h - height) / 2;
r->captureFrame(x, y, width, height, r->captureFrame(x, y, width, height,
Renderer::PixelFormat::RGB, r->getPreferredCaptureFormat(),
pict->data[0]); pict->data[0]);
// Read image is vertically flipped
// TODO: this should go to Renderer::captureFrame()
int realWidth = width * 3; // 3 bytes per pixel
uint8_t *tempLine = new uint8_t[realWidth];
uint8_t *fb = pict->data[0];
for (int i = 0, p = realWidth * (height - 1); i < p; i += realWidth, p -= realWidth)
{
memcpy(tempLine, &fb[i], realWidth);
memcpy(&fb[i], &fb[p], realWidth);
memcpy(&fb[p], tempLine, realWidth);
}
delete[] tempLine;
} }
// encode one video frame and send it to the muxer // encode one video frame and send it to the muxer

View File

@ -43,6 +43,7 @@
#include <vector> #include <vector>
#include <string> #include <string>
#include <cassert> #include <cassert>
#include <celengine/glsupport.h>
#include <celutil/gettext.h> #include <celutil/gettext.h>
#include <celutil/util.h> #include <celutil/util.h>
#include "qtappwin.h" #include "qtappwin.h"
@ -58,7 +59,6 @@
#include "qteventfinder.h" #include "qteventfinder.h"
#include "qtsettimedialog.h" #include "qtsettimedialog.h"
#include "qtgotoobjectdialog.h" #include "qtgotoobjectdialog.h"
//#include "qtvideocapturedialog.h"
#include <celestia/celestiastate.h> #include <celestia/celestiastate.h>
#include <celestia/scriptmenu.h> #include <celestia/scriptmenu.h>
#include <celestia/url.h> #include <celestia/url.h>
@ -600,10 +600,7 @@ void CelestiaAppWindow::slotGrabImage()
if (!saveAsName.isEmpty()) if (!saveAsName.isEmpty())
{ {
//glWidget->repaint(); m_appCore->saveScreenShot(saveAsName.toStdString());
QImage grabbedImage = glWidget->grabFrameBuffer();
grabbedImage.save(saveAsName);
settings.setValue("GrabImageDir", QFileInfo(saveAsName).absolutePath()); settings.setValue("GrabImageDir", QFileInfo(saveAsName).absolutePath());
} }
settings.endGroup(); settings.endGroup();
@ -695,13 +692,30 @@ void CelestiaAppWindow::slotCaptureVideo()
#endif #endif
} }
static QImage::Format toQFormat(PixelFormat format)
{
switch (format)
{
case PixelFormat::RGB:
return QImage::Format_RGB888;
case PixelFormat::RGBA:
return QImage::Format_RGBA8888;
default:
return QImage::Format_Invalid;
}
}
void CelestiaAppWindow::slotCopyImage() void CelestiaAppWindow::slotCopyImage()
{ {
//glWidget->repaint(); //glWidget->repaint();
QImage grabbedImage = glWidget->grabFrameBuffer(); Image image = m_appCore->captureImage();
QImage grabbedImage = QImage(image.getPixels(),
image.getWidth(),
image.getHeight(),
image.getPitch(),
toQFormat(image.getFormat()));
QApplication::clipboard()->setImage(grabbedImage); QApplication::clipboard()->setImage(grabbedImage);
m_appCore->flash(QString(_("Captured screen shot to clipboard")).toStdString()); m_appCore->flash(_("Captured screen shot to clipboard"));
} }
@ -1156,7 +1170,6 @@ void CelestiaAppWindow::createMenus()
QAction* captureVideoAction = new QAction(QIcon(":/icons/capture-video.png"), QAction* captureVideoAction = new QAction(QIcon(":/icons/capture-video.png"),
_("Capture &video"), this); _("Capture &video"), this);
// TODO: Add Mac support for video capture
#ifndef USE_FFMPEG #ifndef USE_FFMPEG
captureVideoAction->setEnabled(false); captureVideoAction->setEnabled(false);
#endif #endif

View File

@ -35,7 +35,6 @@
#include "celutil/filetype.h" #include "celutil/filetype.h"
#include "celutil/debug.h" #include "celutil/debug.h"
#include "celutil/gettext.h" #include "celutil/gettext.h"
#include "celestia/imagecapture.h"
#include "celestia/celestiacore.h" #include "celestia/celestiacore.h"
#include "celengine/simulation.h" #include "celengine/simulation.h"
#ifdef USE_GLCONTEXT #ifdef USE_GLCONTEXT

View File

@ -11,13 +11,13 @@
#include <cstdint> #include <cstdint>
#include <fstream> // ifstream #include <fstream> // ifstream
#include <iostream> // ios #include <iostream> // ios
#include <celengine/glsupport.h>
#include <celengine/image.h> #include <celengine/image.h>
#include <celutil/bytes.h> #include <celutil/bytes.h>
#include <celutil/debug.h> #include <celutil/debug.h>
using std::ifstream; using std::ifstream;
using std::ios; using std::ios;
using celestia::PixelFormat;
namespace namespace
{ {
@ -126,7 +126,7 @@ Image* LoadBMPImage(ifstream& in)
// check for truncated file // check for truncated file
auto* img = new Image(GL_RGB, imageHeader.width, imageHeader.height); auto* img = new Image(PixelFormat::RGB, imageHeader.width, imageHeader.height);
// Copy the image and perform any necessary conversions // Copy the image and perform any necessary conversions
for (int32_t y = 0; y < imageHeader.height; y++) for (int32_t y = 0; y < imageHeader.height; y++)

View File

@ -280,7 +280,7 @@ Image* LoadDDSImage(const fs::path& filename)
} }
} }
Image *img = new Image(transparent0 ? GL_RGB : GL_RGBA, ddsd.width, ddsd.height); Image *img = new Image(transparent0 ? PixelFormat::RGB : PixelFormat::RGBA, ddsd.width, ddsd.height);
memcpy(img->getPixels(), pixels, (transparent0 ? 3 : 4) * ddsd.width * ddsd.height); memcpy(img->getPixels(), pixels, (transparent0 ? 3 : 4) * ddsd.width * ddsd.height);
delete[] pixels; delete[] pixels;
return img; return img;
@ -290,7 +290,7 @@ Image* LoadDDSImage(const fs::path& filename)
// TODO: Verify that the reported texture size matches the amount of // TODO: Verify that the reported texture size matches the amount of
// data expected. // data expected.
Image* img = new Image(format, Image* img = new Image(static_cast<PixelFormat>(format),
(int) ddsd.width, (int) ddsd.width,
(int) ddsd.height, (int) ddsd.height,
max(ddsd.mipMapLevels, 1u)); max(ddsd.mipMapLevels, 1u));

View File

@ -18,6 +18,9 @@ Image* LoadBMPImage(const fs::path& filename);
Image* LoadPNGImage(const fs::path& filename); Image* LoadPNGImage(const fs::path& filename);
Image* LoadDDSImage(const fs::path& filename); Image* LoadDDSImage(const fs::path& filename);
bool SaveJPEGImage(const fs::path& filename, Image& image);
bool SavePNGImage(const fs::path& filename, Image& image);
bool SaveJPEGImage(const fs::path& filename, bool SaveJPEGImage(const fs::path& filename,
int width, int height, int width, int height,
int rowStride, int rowStride,

View File

@ -14,10 +14,11 @@
extern "C" { extern "C" {
#include <jpeglib.h> #include <jpeglib.h>
} }
#include <celengine/glsupport.h>
#include <celengine/image.h> #include <celengine/image.h>
#include <celutil/debug.h> #include <celutil/debug.h>
using celestia::PixelFormat;
namespace namespace
{ {
struct my_error_mgr struct my_error_mgr
@ -130,9 +131,9 @@ Image* LoadJPEGImage(const fs::path& filename, int /*unused*/)
// Here we use the library's state variable cinfo.output_scanline as the // 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. // loop counter, so that we don't have to keep track ourselves.
int format = GL_RGB; PixelFormat format = PixelFormat::RGB;
if (cinfo.output_components == 1) if (cinfo.output_components == 1)
format = GL_LUMINANCE; format = PixelFormat::LUMINANCE;
img = new Image(format, cinfo.image_width, cinfo.image_height); img = new Image(format, cinfo.image_width, cinfo.image_height);
@ -216,7 +217,7 @@ bool SaveJPEGImage(const fs::path& filename,
while (cinfo.next_scanline < cinfo.image_height) while (cinfo.next_scanline < cinfo.image_height)
{ {
unsigned char *rowHead = &pixels[rowStride * (cinfo.image_height - cinfo.next_scanline - 1)]; unsigned char *rowHead = &pixels[rowStride * cinfo.next_scanline];
// Strip alpha values if we are in RGBA format // Strip alpha values if we are in RGBA format
if (removeAlpha) if (removeAlpha)
{ {
@ -239,3 +240,13 @@ bool SaveJPEGImage(const fs::path& filename,
return true; return true;
} }
bool SaveJPEGImage(const fs::path& filename, Image& image)
{
return SaveJPEGImage(filename,
image.getWidth(),
image.getHeight(),
image.getPitch(),
image.getPixels(),
image.hasAlpha());
}

View File

@ -12,13 +12,13 @@
#include <png.h> #include <png.h>
#include <zlib.h> #include <zlib.h>
#include <fmt/printf.h> #include <fmt/printf.h>
#include <celengine/glsupport.h>
#include <celengine/image.h> #include <celengine/image.h>
#include <celutil/debug.h> #include <celutil/debug.h>
#include <celutil/gettext.h> #include <celutil/gettext.h>
using std::cerr; using std::cerr;
using std::clog; using std::clog;
using celestia::PixelFormat;
namespace namespace
{ {
@ -102,28 +102,28 @@ Image* LoadPNGImage(const fs::path& filename)
&color_type, &interlace_type, &color_type, &interlace_type,
nullptr, nullptr); nullptr, nullptr);
GLenum glformat = GL_RGB; PixelFormat format = PixelFormat::RGB;
switch (color_type) switch (color_type)
{ {
case PNG_COLOR_TYPE_GRAY: case PNG_COLOR_TYPE_GRAY:
glformat = GL_LUMINANCE; format = PixelFormat::LUMINANCE;
break; break;
case PNG_COLOR_TYPE_GRAY_ALPHA: case PNG_COLOR_TYPE_GRAY_ALPHA:
glformat = GL_LUMINANCE_ALPHA; format = PixelFormat::LUM_ALPHA;
break; break;
case PNG_COLOR_TYPE_RGB: case PNG_COLOR_TYPE_RGB:
glformat = GL_RGB; format = PixelFormat::RGB;
break; break;
case PNG_COLOR_TYPE_PALETTE: case PNG_COLOR_TYPE_PALETTE:
case PNG_COLOR_TYPE_RGB_ALPHA: case PNG_COLOR_TYPE_RGB_ALPHA:
glformat = GL_RGBA; format = PixelFormat::RGBA;
break; break;
default: default:
// badness // badness
break; break;
} }
img = new Image(glformat, width, height); img = new Image(format, width, height);
// TODO: consider using paletted textures if they're available // TODO: consider using paletted textures if they're available
if (color_type == PNG_COLOR_TYPE_PALETTE) if (color_type == PNG_COLOR_TYPE_PALETTE)
@ -184,7 +184,7 @@ bool SavePNGImage(const fs::path& filename,
auto* row_pointers = new png_bytep[height]; auto* row_pointers = new png_bytep[height];
for (int i = 0; i < height; i++) for (int i = 0; i < height; i++)
{ {
unsigned char *rowHead = &pixels[rowStride * (height - i - 1)]; unsigned char *rowHead = &pixels[rowStride * i];
// Strip alpha values if we are in RGBA format // Strip alpha values if we are in RGBA format
if (removeAlpha) if (removeAlpha)
{ {
@ -256,3 +256,14 @@ bool SavePNGImage(const fs::path& filename,
return true; return true;
} }
bool SavePNGImage(const fs::path& filename, Image& image)
{
return SavePNGImage(filename,
image.getWidth(),
image.getHeight(),
image.getPitch(),
image.getPixels(),
image.hasAlpha());
}

View File

@ -25,7 +25,6 @@
#include <celutil/debug.h> #include <celutil/debug.h>
#include <celutil/gettext.h> #include <celutil/gettext.h>
#include <celestia/celestiacore.h> #include <celestia/celestiacore.h>
#include <celestia/imagecapture.h>
#include <celestia/url.h> #include <celestia/url.h>
#include "celx_internal.h" #include "celx_internal.h"