diff --git a/src/celengine/galaxy.cpp b/src/celengine/galaxy.cpp index 4f9ed3660..3a9326aa5 100644 --- a/src/celengine/galaxy.cpp +++ b/src/celengine/galaxy.cpp @@ -340,7 +340,7 @@ void Galaxy::renderGalaxyPointSprites(const Vector3f& offset, if (galaxyTex == nullptr) { - galaxyTex = CreateProceduralTexture(width, height, GL_RGBA, + galaxyTex = CreateProceduralTexture(width, height, PixelFormat::RGBA, GalaxyTextureEval); } assert(galaxyTex != nullptr); @@ -349,7 +349,7 @@ void Galaxy::renderGalaxyPointSprites(const Vector3f& offset, if (colorTex == nullptr) { - colorTex = CreateProceduralTexture(256, 1, GL_RGBA, + colorTex = CreateProceduralTexture(256, 1, PixelFormat::RGBA, ColorTextureEval, Texture::EdgeClamp, Texture::NoMipMaps); diff --git a/src/celengine/globular.cpp b/src/celengine/globular.cpp index 192881785..7bb531291 100644 --- a/src/celengine/globular.cpp +++ b/src/celengine/globular.cpp @@ -430,14 +430,16 @@ void Globular::renderGlobularPointSprites( if(centerTex[ic] == nullptr) { - centerTex[ic] = CreateProceduralTexture(cntrTexWidth, cntrTexHeight, GL_RGBA, + centerTex[ic] = CreateProceduralTexture(cntrTexWidth, cntrTexHeight, + PixelFormat::RGBA, CenterCloudTexEval); } assert(centerTex[ic] != nullptr); if (globularTex == nullptr) { - globularTex = CreateProceduralTexture(starTexWidth, starTexHeight, GL_RGBA, + globularTex = CreateProceduralTexture(starTexWidth, starTexHeight, + PixelFormat::RGBA, GlobularTextureEval); } assert(globularTex != nullptr); diff --git a/src/celengine/glsupport.cpp b/src/celengine/glsupport.cpp index 3dda20720..989fcdbb2 100644 --- a/src/celengine/glsupport.cpp +++ b/src/celengine/glsupport.cpp @@ -15,6 +15,7 @@ bool EXT_framebuffer_object = false; bool ARB_shader_texture_lod = false; bool EXT_texture_compression_s3tc = false; bool EXT_texture_filter_anisotropic = false; +bool MESA_pack_invert = false; GLint maxPointSize = 0; GLfloat maxLineWidth = 0.0f; @@ -42,6 +43,7 @@ bool init(util::array_view ignore) noexcept 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_filter_anisotropic = check_extension(ignore, "GL_EXT_texture_filter_anisotropic"); + MESA_pack_invert = check_extension(ignore, "GL_MESA_pack_invert"); GLint pointSizeRange[2]; GLfloat lineWidthRange[2]; diff --git a/src/celengine/glsupport.h b/src/celengine/glsupport.h index fd9c302c8..a94d118f2 100644 --- a/src/celengine/glsupport.h +++ b/src/celengine/glsupport.h @@ -40,6 +40,7 @@ constexpr const int GLES_2 = 20; extern bool ARB_shader_texture_lod; extern bool EXT_texture_compression_s3tc; extern bool EXT_texture_filter_anisotropic; +extern bool MESA_pack_invert; #ifdef GL_ES extern bool OES_vertex_array_object; #else diff --git a/src/celengine/image.cpp b/src/celengine/image.cpp index d42f210bd..b7aca86fd 100644 --- a/src/celengine/image.cpp +++ b/src/celengine/image.cpp @@ -19,6 +19,7 @@ #include "image.h" using namespace std; +using celestia::PixelFormat; namespace { @@ -28,32 +29,27 @@ int pad(int n) return (n + 3) & ~0x3; } -int formatComponents(int fmt) +int formatComponents(PixelFormat fmt) { switch (fmt) { - case GL_RGBA: - case GL_BGRA_EXT: + case PixelFormat::RGBA: + case PixelFormat::BGRA: return 4; - case GL_RGB: -#ifndef GL_ES - case GL_BGR_EXT: -#endif + case PixelFormat::RGB: + case PixelFormat::BGR: return 3; - case GL_LUMINANCE_ALPHA: -#ifndef GL_ES - case GL_DSDT_NV: -#endif + case PixelFormat::LUM_ALPHA: return 2; - case GL_ALPHA: - case GL_LUMINANCE: + case PixelFormat::ALPHA: + case PixelFormat::LUMINANCE: return 1; // Compressed formats - case GL_COMPRESSED_RGBA_S3TC_DXT1_EXT: + case PixelFormat::DXT1: return 3; - case GL_COMPRESSED_RGBA_S3TC_DXT3_EXT: - case GL_COMPRESSED_RGBA_S3TC_DXT5_EXT: + case PixelFormat::DXT3: + case PixelFormat::DXT5: return 4; // 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); h = max(h >> mip, 1); switch (fmt) { - case GL_COMPRESSED_RGBA_S3TC_DXT1_EXT: + case PixelFormat::DXT1: // 4x4 blocks, 8 bytes per block return ((w + 3) / 4) * ((h + 3) / 4) * 8; - case GL_COMPRESSED_RGBA_S3TC_DXT3_EXT: - case GL_COMPRESSED_RGBA_S3TC_DXT5_EXT: + case PixelFormat::DXT3: + case PixelFormat::DXT5: // 4x4 blocks, 16 bytes per block return ((w + 3) / 4) * ((h + 3) / 4) * 16; default: @@ -82,7 +78,7 @@ int calcMipLevelSize(int fmt, int w, int h, int mip) } } // anonymous namespace -Image::Image(int fmt, int w, int h, int mip) : +Image::Image(PixelFormat fmt, int w, int h, int mip) : width(w), height(h), mipLevels(mip), @@ -96,12 +92,12 @@ Image::Image(int fmt, int w, int h, int mip) : size = 1; for (int i = 0; i < mipLevels; i++) size += calcMipLevelSize(fmt, w, h, i); - pixels = new unsigned char[size]; + pixels = make_unique(size); } -Image::~Image() +bool Image::isValid() const noexcept { - delete[] pixels; + return pixels != nullptr; } int Image::getWidth() const @@ -129,7 +125,7 @@ int Image::getSize() const return size; } -int Image::getFormat() const +PixelFormat Image::getFormat() const { return format; } @@ -139,12 +135,12 @@ int Image::getComponents() const 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 h = max(height >> mip, 1); @@ -158,12 +154,12 @@ unsigned char* Image::getPixelRow(int mip, int row) return getMipLevel(mip) + row * pitch; } -unsigned char* Image::getPixelRow(int row) +uint8_t* Image::getPixelRow(int row) { return getPixelRow(0, row); } -unsigned char* Image::getMipLevel(int mip) +uint8_t* Image::getMipLevel(int mip) { if (mip >= mipLevels) return nullptr; @@ -172,7 +168,7 @@ unsigned char* Image::getMipLevel(int mip) for (int i = 0; i < mip; i++) offset += calcMipLevelSize(format, width, height, i); - return pixels + offset; + return pixels.get() + offset; } int Image::getMipLevelSize(int mip) const @@ -187,9 +183,9 @@ bool Image::isCompressed() const { switch (format) { - case GL_COMPRESSED_RGBA_S3TC_DXT1_EXT: - case GL_COMPRESSED_RGBA_S3TC_DXT3_EXT: - case GL_COMPRESSED_RGBA_S3TC_DXT5_EXT: + case PixelFormat::DXT1: + case PixelFormat::DXT3: + case PixelFormat::DXT5: return true; default: return false; @@ -200,12 +196,12 @@ bool Image::hasAlpha() const { switch (format) { - case GL_COMPRESSED_RGBA_S3TC_DXT3_EXT: - case GL_COMPRESSED_RGBA_S3TC_DXT5_EXT: - case GL_RGBA: - case GL_BGRA_EXT: - case GL_LUMINANCE_ALPHA: - case GL_ALPHA: + case PixelFormat::DXT3: + case PixelFormat::DXT5: + case PixelFormat::RGBA: + case PixelFormat::BGRA: + case PixelFormat::LUM_ALPHA: + case PixelFormat::ALPHA: return true; default: return false; @@ -225,9 +221,9 @@ Image* Image::computeNormalMap(float scale, bool wrap) const if (isCompressed()) 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(); // Compute normals using differences between adjacent texels. @@ -275,9 +271,9 @@ Image* Image::computeNormalMap(float scale, bool wrap) const float rmag = 1.0f / mag; int n = i * nmPitch + j * 4; - nmPixels[n] = (unsigned char) (128 + 127 * dx * rmag); - nmPixels[n + 1] = (unsigned char) (128 + 127 * dy * rmag); - nmPixels[n + 2] = (unsigned char) (128 + 127 * rmag); + nmPixels[n] = (uint8_t) (128 + 127 * dx * rmag); + nmPixels[n + 1] = (uint8_t) (128 + 127 * dy * rmag); + nmPixels[n + 2] = (uint8_t) (128 + 127 * rmag); nmPixels[n + 3] = 255; } } diff --git a/src/celengine/image.h b/src/celengine/image.h index 84460a29a..afe2ce3ed 100644 --- a/src/celengine/image.h +++ b/src/celengine/image.h @@ -10,7 +10,9 @@ #pragma once +#include #include +#include // The image class supports multiple GL formats, including compressed ones. // Mipmaps may be stored within an image as well. The mipmaps are stored in @@ -21,19 +23,24 @@ class Image { public: - Image(int fmt, int w, int h, int mip = 1); - ~Image(); + Image(celestia::PixelFormat format, int w, int h, int mip = 1); + ~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 getHeight() const; int getPitch() const; int getMipLevelCount() const; - int getFormat() const; + celestia::PixelFormat getFormat() const; int getComponents() const; - unsigned char* getPixels(); - unsigned char* getPixelRow(int row); - unsigned char* getPixelRow(int mip, int row); - unsigned char* getMipLevel(int mip); + uint8_t* getPixels(); + uint8_t* getPixelRow(int row); + uint8_t* getPixelRow(int mip, int row); + uint8_t* getMipLevel(int mip); int getSize() const; int getMipLevelSize(int mip) const; @@ -54,9 +61,9 @@ class Image int pitch; int mipLevels; int components; - int format; + celestia::PixelFormat format; int size; - unsigned char* pixels{ nullptr }; + std::unique_ptr pixels; }; Image* LoadImageFromFile(const fs::path& filename); diff --git a/src/celengine/pixelformat.h b/src/celengine/pixelformat.h new file mode 100644 index 000000000..d7bcc9bd8 --- /dev/null +++ b/src/celengine/pixelformat.h @@ -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 +}; +} diff --git a/src/celengine/render.cpp b/src/celengine/render.cpp index 1e5e96a59..35bcb11f8 100644 --- a/src/celengine/render.cpp +++ b/src/celengine/render.cpp @@ -33,7 +33,6 @@ std::ofstream hdrlog; #define EXPOSURE_HALFLIFE 0.4f #endif -#include #include "render.h" #include "boundaries.h" #include "dsorenderer.h" @@ -85,6 +84,10 @@ std::ofstream hdrlog; #ifdef USE_GLCONTEXT #include "glcontext.h" #endif +#ifdef _MSC_VER +#include +#define alloca(s) _alloca(s) +#endif using namespace cmod; using namespace Eigen; @@ -462,7 +465,7 @@ static void BuildGlareMipLevel2(unsigned char* mipPixels, static Texture* BuildGaussianDiscTexture(unsigned int 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++) { @@ -486,7 +489,7 @@ static Texture* BuildGaussianDiscTexture(unsigned int log2size) static Texture* BuildGaussianGlareTexture(unsigned int 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++) { @@ -600,7 +603,7 @@ bool Renderer::init( } #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, Texture::EdgeClamp, Texture::NoMipMaps); @@ -635,6 +638,9 @@ bool Renderer::init( glEnable(GL_CULL_FACE); glCullFace(GL_BACK); + if (gl::MESA_pack_invert) + glPixelStorei(GL_PACK_INVERT_MESA, GL_TRUE); + // LEQUAL rather than LESS required for multipass rendering 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; } -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 - glReadBuffer(back ? GL_BACK : GL_FRONT); + || format == PixelFormat::BGR #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(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) diff --git a/src/celengine/render.h b/src/celengine/render.h index 24c6a7670..5b393c538 100644 --- a/src/celengine/render.h +++ b/src/celengine/render.h @@ -252,17 +252,6 @@ class Renderer 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; void setRenderFlags(uint64_t); int getLabelMode() const; @@ -314,6 +303,8 @@ class Renderer void enableDepthTest() 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 setRenderRegion(int x, int y, int width, int height, bool withScissor = true); @@ -324,7 +315,7 @@ class Renderer void setSolarSystemMaxDistance(float); 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, float size, diff --git a/src/celengine/texture.cpp b/src/celengine/texture.cpp index 50e8c0239..0d90299b4 100644 --- a/src/celengine/texture.cpp +++ b/src/celengine/texture.cpp @@ -7,7 +7,6 @@ // as published by the Free Software Foundation; either version 2 // of the License, or (at your option) any later version. -#include #include #include #include @@ -117,40 +116,37 @@ static const TextureCaps& GetTextureCaps() } - -static int getInternalFormat(int format) +static int getInternalFormat(PixelFormat format) { #ifdef GL_ES switch (format) { - case GL_RGBA: - case GL_RGB: - case GL_LUMINANCE_ALPHA: - case GL_ALPHA: - case GL_LUMINANCE: - case GL_COMPRESSED_RGBA_S3TC_DXT1_EXT: - case GL_COMPRESSED_RGBA_S3TC_DXT3_EXT: - case GL_COMPRESSED_RGBA_S3TC_DXT5_EXT: - return format; + case PixelFormat::RGBA: + case PixelFormat::RGB: + case PixelFormat::LUM_ALPHA: + case PixelFormat::ALPHA: + case PixelFormat::LUMINANCE: + case PixelFormat::DXT1: + case PixelFormat::DXT3: + case PixelFormat::DXT5: + return (int) format; default: return 0; } #else switch (format) { - case GL_RGBA: - case GL_BGRA: - case GL_RGB: - case GL_BGR: - case GL_LUMINANCE_ALPHA: - case GL_ALPHA: - case GL_INTENSITY: - case GL_LUMINANCE: - case GL_DSDT_NV: - case GL_COMPRESSED_RGBA_S3TC_DXT1_EXT: - case GL_COMPRESSED_RGBA_S3TC_DXT3_EXT: - case GL_COMPRESSED_RGBA_S3TC_DXT5_EXT: - return format; + case PixelFormat::RGBA: + case PixelFormat::BGRA: + case PixelFormat::RGB: + case PixelFormat::BGR: + case PixelFormat::LUM_ALPHA: + case PixelFormat::ALPHA: + case PixelFormat::LUMINANCE: + case PixelFormat::DXT1: + case PixelFormat::DXT3: + case PixelFormat::DXT5: + return (int) format; default: return 0; } @@ -190,9 +186,9 @@ static int getCompressedInternalFormat(int format) #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[]) : Texture(faces[0]->getWidth(), faces[0]->getHeight()), glName(0) { // Verify that all the faces are square and have the same size int width = faces[0]->getWidth(); - int format = faces[0]->getFormat(); + PixelFormat format = faces[0]->getFormat(); int i = 0; for (i = 0; i < 6; i++) { @@ -855,7 +850,7 @@ void CubeMap::setBorderColor(Color borderColor) Texture* CreateProceduralTexture(int width, int height, - int format, + PixelFormat format, ProceduralTexEval func, Texture::AddressMode addressMode, Texture::MipMapMode mipMode) @@ -880,7 +875,7 @@ Texture* CreateProceduralTexture(int width, int height, Texture* CreateProceduralTexture(int width, int height, - int format, + PixelFormat format, TexelFunctionObject& func, Texture::AddressMode addressMode, Texture::MipMapMode mipMode) @@ -940,8 +935,9 @@ static Vector3f cubeVector(int face, float s, float t) } -extern Texture* CreateProceduralCubeMap(int size, int format, - ProceduralTexEval func) +Texture* CreateProceduralCubeMap(int size, + PixelFormat format, + ProceduralTexEval func) { Image* faces[6]; @@ -1056,7 +1052,7 @@ Texture* LoadTextureFromFile(const fs::path& filename, // compressed normal map. There's no separate OpenGL format for dxt5 // normal maps, so the file extension is the only thing that // 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); } diff --git a/src/celengine/texture.h b/src/celengine/texture.h index 45e298f9a..463343ef2 100644 --- a/src/celengine/texture.h +++ b/src/celengine/texture.h @@ -168,16 +168,16 @@ class CubeMap : public Texture extern Texture* CreateProceduralTexture(int width, int height, - int format, + celestia::PixelFormat format, ProceduralTexEval func, Texture::AddressMode addressMode = Texture::EdgeClamp, Texture::MipMapMode mipMode = Texture::DefaultMipMaps); extern Texture* CreateProceduralTexture(int width, int height, - int format, + celestia::PixelFormat format, TexelFunctionObject& func, Texture::AddressMode addressMode = Texture::EdgeClamp, Texture::MipMapMode mipMode = Texture::DefaultMipMaps); -extern Texture* CreateProceduralCubeMap(int size, int format, +extern Texture* CreateProceduralCubeMap(int size, celestia::PixelFormat format, ProceduralTexEval func); extern Texture* LoadTextureFromFile(const fs::path& filename, diff --git a/src/celestia/CMakeLists.txt b/src/celestia/CMakeLists.txt index 254dca6cb..1fb89792e 100644 --- a/src/celestia/CMakeLists.txt +++ b/src/celestia/CMakeLists.txt @@ -13,8 +13,6 @@ set(CELESTIA_SOURCES favorites.h helper.cpp helper.h - imagecapture.cpp - imagecapture.h scriptmenu.cpp scriptmenu.h url.cpp diff --git a/src/celestia/celestiacore.cpp b/src/celestia/celestiacore.cpp index efaa13a4c..c6c2deb1e 100644 --- a/src/celestia/celestiacore.cpp +++ b/src/celestia/celestiacore.cpp @@ -31,6 +31,7 @@ #include #include #include +#include #include #include #include @@ -60,12 +61,11 @@ #endif #include -#include "imagecapture.h" - using namespace Eigen; using namespace std; using namespace astro::literals; using namespace celmath; +using namespace celestia; using namespace celestia::scripts; using namespace celestia::util; @@ -4719,20 +4719,49 @@ View* CelestiaCore::getViewByObserver(const Observer *obs) const return nullptr; } +Image CelestiaCore::captureImage() const +{ + // Get the dimensions of the current viewport + array 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 { if (type == Content_Unknown) type = DetermineFileType(filename); - // Get the dimensions of the current viewport - array viewport; - getRenderer()->getViewport(viewport); + if (type != Content_JPEG && type != Content_PNG) + { + fmt::print(cerr, _("Unsupported image type: {}!\n"), filename.string()); + return false; + } - return CaptureBufferToFile(filename, - viewport[0], viewport[1], - viewport[2], viewport[3], - getRenderer(), - type); + Image image = captureImage(); + if (!image.isValid()) + return false; + + 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) diff --git a/src/celestia/celestiacore.h b/src/celestia/celestiacore.h index d44264458..79c7f7d1b 100644 --- a/src/celestia/celestiacore.h +++ b/src/celestia/celestiacore.h @@ -386,6 +386,7 @@ class CelestiaCore // : public Watchable void setScriptHook(std::unique_ptr &&hook) { m_scriptHook = std::move(hook); } const std::shared_ptr& scriptMaps() const { return m_scriptMaps; } + Image captureImage() const; bool saveScreenShot(const fs::path&, ContentType = Content_Unknown) const; protected: diff --git a/src/celestia/imagecapture.cpp b/src/celestia/imagecapture.cpp deleted file mode 100644 index ddac93e99..000000000 --- a/src/celestia/imagecapture.cpp +++ /dev/null @@ -1,65 +0,0 @@ -// imagecapture.cpp -// -// Copyright (C) 2001-present, the Celestia Development Team -// Original version by Chris Laurel -// -// 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 -#include -#include -#include -#include -#include -#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(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; -} diff --git a/src/celestia/imagecapture.h b/src/celestia/imagecapture.h deleted file mode 100644 index 1f5e4904d..000000000 --- a/src/celestia/imagecapture.h +++ /dev/null @@ -1,22 +0,0 @@ -// imagecapture.h -// -// Copyright (C) 2001-present, the Celestia Development Team -// Original version by Chris Laurel -// -// 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 -#include - -class Renderer; - -bool CaptureBufferToFile(const fs::path& filename, - int x, int y, - int width, int height, - const Renderer *renderer, - ContentType type); diff --git a/src/celestia/moviecapture.cpp b/src/celestia/moviecapture.cpp index 19a8c6d1f..49a6f2ee9 100644 --- a/src/celestia/moviecapture.cpp +++ b/src/celestia/moviecapture.cpp @@ -411,21 +411,8 @@ static void captureImage(AVFrame *pict, int width, int height, const Renderer *r x += (w - width) / 2; y += (h - height) / 2; r->captureFrame(x, y, width, height, - Renderer::PixelFormat::RGB, + r->getPreferredCaptureFormat(), 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 diff --git a/src/celestia/qt/qtappwin.cpp b/src/celestia/qt/qtappwin.cpp index 61f2bcd1c..5df622740 100644 --- a/src/celestia/qt/qtappwin.cpp +++ b/src/celestia/qt/qtappwin.cpp @@ -43,6 +43,7 @@ #include #include #include +#include #include #include #include "qtappwin.h" @@ -58,7 +59,6 @@ #include "qteventfinder.h" #include "qtsettimedialog.h" #include "qtgotoobjectdialog.h" -//#include "qtvideocapturedialog.h" #include #include #include @@ -600,10 +600,7 @@ void CelestiaAppWindow::slotGrabImage() if (!saveAsName.isEmpty()) { - //glWidget->repaint(); - QImage grabbedImage = glWidget->grabFrameBuffer(); - grabbedImage.save(saveAsName); - + m_appCore->saveScreenShot(saveAsName.toStdString()); settings.setValue("GrabImageDir", QFileInfo(saveAsName).absolutePath()); } settings.endGroup(); @@ -695,13 +692,30 @@ void CelestiaAppWindow::slotCaptureVideo() #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() { //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); - 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"), _("Capture &video"), this); - // TODO: Add Mac support for video capture #ifndef USE_FFMPEG captureVideoAction->setEnabled(false); #endif diff --git a/src/celestia/qt/qtglwidget.cpp b/src/celestia/qt/qtglwidget.cpp index a4217514b..f27dc54bd 100644 --- a/src/celestia/qt/qtglwidget.cpp +++ b/src/celestia/qt/qtglwidget.cpp @@ -35,7 +35,6 @@ #include "celutil/filetype.h" #include "celutil/debug.h" #include "celutil/gettext.h" -#include "celestia/imagecapture.h" #include "celestia/celestiacore.h" #include "celengine/simulation.h" #ifdef USE_GLCONTEXT diff --git a/src/celimage/bmp.cpp b/src/celimage/bmp.cpp index 7430e2ccd..0a60f0f11 100644 --- a/src/celimage/bmp.cpp +++ b/src/celimage/bmp.cpp @@ -11,13 +11,13 @@ #include #include // ifstream #include // ios -#include #include #include #include using std::ifstream; using std::ios; +using celestia::PixelFormat; namespace { @@ -126,7 +126,7 @@ Image* LoadBMPImage(ifstream& in) // 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 for (int32_t y = 0; y < imageHeader.height; y++) diff --git a/src/celimage/dds.cpp b/src/celimage/dds.cpp index c045fedeb..f4b829ef9 100644 --- a/src/celimage/dds.cpp +++ b/src/celimage/dds.cpp @@ -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); delete[] pixels; return img; @@ -290,7 +290,7 @@ Image* LoadDDSImage(const fs::path& filename) // TODO: Verify that the reported texture size matches the amount of // data expected. - Image* img = new Image(format, + Image* img = new Image(static_cast(format), (int) ddsd.width, (int) ddsd.height, max(ddsd.mipMapLevels, 1u)); diff --git a/src/celimage/imageformats.h b/src/celimage/imageformats.h index a109b3595..af65de55c 100644 --- a/src/celimage/imageformats.h +++ b/src/celimage/imageformats.h @@ -18,6 +18,9 @@ Image* LoadBMPImage(const fs::path& filename); Image* LoadPNGImage(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, int width, int height, int rowStride, diff --git a/src/celimage/jpeg.cpp b/src/celimage/jpeg.cpp index f2944c6ae..9ece91cc5 100644 --- a/src/celimage/jpeg.cpp +++ b/src/celimage/jpeg.cpp @@ -14,10 +14,11 @@ extern "C" { #include } -#include #include #include +using celestia::PixelFormat; + namespace { 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 // 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) - format = GL_LUMINANCE; + format = PixelFormat::LUMINANCE; 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) { - 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 if (removeAlpha) { @@ -239,3 +240,13 @@ bool SaveJPEGImage(const fs::path& filename, return true; } + +bool SaveJPEGImage(const fs::path& filename, Image& image) +{ + return SaveJPEGImage(filename, + image.getWidth(), + image.getHeight(), + image.getPitch(), + image.getPixels(), + image.hasAlpha()); +} diff --git a/src/celimage/png.cpp b/src/celimage/png.cpp index e04683afd..a63b4c96c 100644 --- a/src/celimage/png.cpp +++ b/src/celimage/png.cpp @@ -12,13 +12,13 @@ #include #include #include -#include #include #include #include using std::cerr; using std::clog; +using celestia::PixelFormat; namespace { @@ -102,28 +102,28 @@ Image* LoadPNGImage(const fs::path& filename) &color_type, &interlace_type, nullptr, nullptr); - GLenum glformat = GL_RGB; + PixelFormat format = PixelFormat::RGB; switch (color_type) { case PNG_COLOR_TYPE_GRAY: - glformat = GL_LUMINANCE; + format = PixelFormat::LUMINANCE; break; case PNG_COLOR_TYPE_GRAY_ALPHA: - glformat = GL_LUMINANCE_ALPHA; + format = PixelFormat::LUM_ALPHA; break; case PNG_COLOR_TYPE_RGB: - glformat = GL_RGB; + format = PixelFormat::RGB; break; case PNG_COLOR_TYPE_PALETTE: case PNG_COLOR_TYPE_RGB_ALPHA: - glformat = GL_RGBA; + format = PixelFormat::RGBA; break; default: // badness break; } - img = new Image(glformat, width, height); + img = new Image(format, width, height); // TODO: consider using paletted textures if they're available if (color_type == PNG_COLOR_TYPE_PALETTE) @@ -184,7 +184,7 @@ bool SavePNGImage(const fs::path& filename, auto* row_pointers = new png_bytep[height]; 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 if (removeAlpha) { @@ -256,3 +256,14 @@ bool SavePNGImage(const fs::path& filename, return true; } + +bool SavePNGImage(const fs::path& filename, Image& image) +{ + return SavePNGImage(filename, + image.getWidth(), + image.getHeight(), + image.getPitch(), + image.getPixels(), + image.hasAlpha()); +} + diff --git a/src/celscript/lua/celx.cpp b/src/celscript/lua/celx.cpp index cbec6c77b..3615e871d 100644 --- a/src/celscript/lua/celx.cpp +++ b/src/celscript/lua/celx.cpp @@ -25,7 +25,6 @@ #include #include #include -#include #include #include "celx_internal.h"