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)
{
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);

View File

@ -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);

View File

@ -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<std::string> 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];

View File

@ -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

View File

@ -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<uint8_t[]>(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;
}
}

View File

@ -10,7 +10,9 @@
#pragma once
#include <memory>
#include <celcompat/filesystem.h>
#include <celengine/pixelformat.h>
// 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<uint8_t[]> pixels;
};
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
#endif
#include <config.h>
#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 <malloc.h>
#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<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)

View File

@ -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,

View File

@ -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 <config.h>
#include <algorithm>
#include <cassert>
#include <cstdlib>
@ -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);
}

View File

@ -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,

View File

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

View File

@ -31,6 +31,7 @@
#include <celengine/planetgrid.h>
#include <celengine/visibleregion.h>
#include <celengine/framebuffer.h>
#include <celimage/imageformats.h>
#include <celmath/geomutil.h>
#include <celutil/color.h>
#include <celutil/filetype.h>
@ -60,12 +61,11 @@
#endif
#include <celttf/truetypefont.h>
#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<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
{
if (type == Content_Unknown)
type = DetermineFileType(filename);
// Get the dimensions of the current viewport
array<int, 4> 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)

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); }
const std::shared_ptr<celestia::scripts::ScriptMaps>& scriptMaps() const { return m_scriptMaps; }
Image captureImage() const;
bool saveScreenShot(const fs::path&, ContentType = Content_Unknown) const;
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;
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

View File

@ -43,6 +43,7 @@
#include <vector>
#include <string>
#include <cassert>
#include <celengine/glsupport.h>
#include <celutil/gettext.h>
#include <celutil/util.h>
#include "qtappwin.h"
@ -58,7 +59,6 @@
#include "qteventfinder.h"
#include "qtsettimedialog.h"
#include "qtgotoobjectdialog.h"
//#include "qtvideocapturedialog.h"
#include <celestia/celestiastate.h>
#include <celestia/scriptmenu.h>
#include <celestia/url.h>
@ -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

View File

@ -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

View File

@ -11,13 +11,13 @@
#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;
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++)

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);
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<PixelFormat>(format),
(int) ddsd.width,
(int) ddsd.height,
max(ddsd.mipMapLevels, 1u));

View File

@ -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,

View File

@ -14,10 +14,11 @@
extern "C" {
#include <jpeglib.h>
}
#include <celengine/glsupport.h>
#include <celengine/image.h>
#include <celutil/debug.h>
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());
}

View File

@ -12,13 +12,13 @@
#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;
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());
}

View File

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