Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions src/include/ipebase.h
Original file line number Diff line number Diff line change
Expand Up @@ -391,6 +391,8 @@ class Platform {
static void initLib(int version);
static void setDebug(bool debug);
static String currentDirectory();
static void changeDirectory(String dir);
static String parentDirectory(String dir);
static String latexPath();
static bool fileExists(String fname);
static bool listDirectory(String path, std::vector<String> & files);
Expand Down
43 changes: 37 additions & 6 deletions src/include/ipebitmap.h
Original file line number Diff line number Diff line change
Expand Up @@ -43,11 +43,12 @@ namespace ipe {
class Bitmap {
public:
enum Flags {
ERGB = 0x01, // not grayscale
EAlpha = 0x02, // has alpha channel
EDCT = 0x04, // DCT encoded jpg image
EInflate = 0x08, // data needs to be inflated
ENative = 0x10, // data is already in native-endian ARGB32
ERGB = 0x01, // not grayscale
EAlpha = 0x02, // has alpha channel
EDCT = 0x04, // DCT encoded jpg image
EInflate = 0x08, // data needs to be inflated
ENative = 0x10, // data is already in native-endian ARGB32
EExternal = 0x20, // image is stored externally
};

Bitmap();
Expand All @@ -62,22 +63,28 @@ class Bitmap {
void saveAsXml(Stream & stream, int id, int pdfObjNum = -1) const;

inline bool isNull() const;
inline bool isLoaded() const;
bool equal(Bitmap rhs) const;

inline int width() const;
inline int height() const;

inline bool isJpeg() const;
inline bool isGray() const;
inline bool isExternal() const;
inline String externalPath() const;
inline bool hasAlpha() const;
inline int colorKey() const;

inline void setExternalPath(String path);
void changeExternalPathRelativeBase(String new_base);

Buffer pixelData();

inline int objNum() const;
inline void setObjNum(int objNum) const;

std::pair<Buffer, Buffer> embed() const;
std::pair<Buffer, Buffer> getEmbedData() const;

inline bool operator==(const Bitmap & rhs) const;
inline bool operator!=(const Bitmap & rhs) const;
Expand All @@ -87,7 +94,12 @@ class Bitmap {
Vector & dotsPerInch, uint32_t & flags);
static Bitmap readJpeg(const char * fname, Vector & dotsPerInch,
const char *& errmsg);
static const char * readPNGData(const char * fname, int & width, int & height,
uint32_t & flags, Buffer & pixels,
Vector & dotsPerInch);
static Bitmap readPNG(const char * fname, Vector & dotsPerInch, const char *& errmsg);
static Bitmap readExternal(String path, const XmlAttributes & attr,
const char *& errmsg);

void savePixels(const char * fname);

Expand All @@ -109,6 +121,7 @@ class Bitmap {
bool iPixelsComputed;
uint32_t iChecksum;
mutable int iObjNum; // Object number (e.g. in PDF file)
String iPath;
};

Imp * iImp;
Expand All @@ -119,6 +132,11 @@ class Bitmap {
//! Is this a null bitmap?
inline bool Bitmap::isNull() const { return (iImp == nullptr); }

//! Could the pixel data for this bitmap be loaded?
inline bool Bitmap::isLoaded() const {
return !isNull() && (iImp->iData.size() > 0 || iImp->iPixelData.size() > 0);
}

//! Return width of pixel array.
inline int Bitmap::width() const { return iImp->iWidth; }

Expand All @@ -131,6 +149,19 @@ inline bool Bitmap::isJpeg() const { return (iImp->iFlags & EDCT) != 0; }
//! Is the bitmap grayscale?
inline bool Bitmap::isGray() const { return (iImp->iFlags & ERGB) == 0; }

//! Is the bitmap stored externally or embedded?
inline bool Bitmap::isExternal() const { return (iImp->iFlags & EExternal) != 0; }

//! The path to the external bitmap file
inline String Bitmap::externalPath() const { return iImp->iPath; }

//! Set the path to the external bitmap file
inline void Bitmap::setExternalPath(String path) {
assert(isExternal());
assert(!path.empty());
iImp->iPath = path;
}

//! Does the bitmap have transparency?
/*! Bitmaps with color key will return false here. */
inline bool Bitmap::hasAlpha() const { return (iImp->iFlags & EAlpha) != 0; }
Expand Down
99 changes: 94 additions & 5 deletions src/ipelib/ipebitmap.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,8 @@

#include "ipebitmap.h"
#include "ipeutils.h"

#include <filesystem>
#include <zlib.h>

using namespace ipe;
Expand Down Expand Up @@ -212,7 +214,8 @@ void Bitmap::unpack(Buffer alphaChannel) {
iImp->iData = pixels;
}

//! Determine if bitmap has alpha channel, colorkey, rgb values (does nothing for JPG).
//! Determine if bitmap has alpha channel, colorkey, rgb values (does nothing for
//! JPG).
void Bitmap::analyze() {
iImp->iColorKey = -1;
iImp->iFlags &= EDCT | ERGB; // clear all other flags, we recompute them
Expand Down Expand Up @@ -310,10 +313,14 @@ void Bitmap::saveAsXml(Stream & stream, int id, int pdfObjNum) const {
if (pdfObjNum >= 0) {
stream << " pdfObject=\"" << pdfObjNum;
if (hasAlpha()) stream << " " << pdfObjNum - 1;
stream << "\"/>\n";
} else {
stream << "\"";
}

if (isExternal()) {
stream << " path=\"" << externalPath() << "\"/>\n";
} else if (pdfObjNum < 0) {
// save data
auto data = embed();
auto data = getEmbedData();
stream << " length=\"" << data.first.size() << "\"";
if (hasAlpha()) stream << " alphaLength=\"" << data.second.size() << "\"";
stream << " encoding=\"base64\">\n";
Expand All @@ -325,6 +332,8 @@ void Bitmap::saveAsXml(Stream & stream, int id, int pdfObjNum) const {
}
b64.close();
stream << "</bitmap>\n";
} else {
stream << "/>";
}
}

Expand Down Expand Up @@ -352,7 +361,7 @@ void Bitmap::computeChecksum() { iImp->iChecksum = iImp->iData.checksum(); }
/*! For Jpeg images, this is simply the bitmap data. For other
images, rgb/grayscale data and alpha channel are split and deflated
separately. */
std::pair<Buffer, Buffer> Bitmap::embed() const {
std::pair<Buffer, Buffer> Bitmap::getEmbedData() const {
if (isJpeg()) return std::make_pair(iImp->iData, Buffer());
int npixels = width() * height();
uint32_t * src = (uint32_t *)iImp->iData.data();
Expand Down Expand Up @@ -667,4 +676,84 @@ Bitmap Bitmap::readJpeg(const char * fname, Vector & dotsPerInch, const char *&
return Bitmap(width, height, flags, Buffer(a.data(), a.size()));
}

//! Read PNG image from file.
/*! Returns the image as a Bitmap.
Sets \a dotsPerInch if the image file contains a resolution,
otherwise sets it to (0,0).
If reading the file fails, returns a null Bitmap,
and sets the error message \a errmsg.
*/
Bitmap Bitmap::readPNG(const char * fname, Vector & dotsPerInch, const char *& errmsg) {
int width;
int height;
uint32_t flags;
Buffer pixels;
errmsg = readPNGData(fname, width, height, flags, pixels, dotsPerInch);

if (!errmsg) {
return Bitmap(width, height, flags, pixels);
} else {
return Bitmap();
}
}

// --------------------------------------------------------------------

Bitmap Bitmap::readExternal(String pathAttr, const XmlAttributes & attr,
const char *& errmsg) {
Bitmap ret;
ret.init(attr);
ret.iImp->iPath = Lex(pathAttr).nextToken();
String path(Platform::realPath(ret.iImp->iPath));
Vector dotsPerInch;

errmsg = nullptr;
const char * errjpg = nullptr;
const char * errpng = readPNGData(path.z(), ret.iImp->iWidth, ret.iImp->iHeight,
ret.iImp->iFlags, ret.iImp->iData, dotsPerInch);
if (errpng) {
FILE * file = Platform::fopen(path.z(), "rb");
if (!file) {
errmsg = "Error opening file";
} else {
errjpg = readJpegInfo(file, ret.iImp->iWidth, ret.iImp->iHeight, dotsPerInch,
ret.iImp->iFlags);
fclose(file);
if (errjpg) {
errmsg = "Could not parse image file as PNG or JPEG";
} else {
String a = Platform::readFile(path);
ret.iImp->iData = Buffer(a.data(), a.size());
}
}
}

if (!errmsg) {
assert(ret.iImp->iWidth > 0 && ret.iImp->iHeight > 0);
assert(ret.iImp->iData.size() > 0);
ret.unpack(Buffer());
ret.computeChecksum();
ret.analyze();
ret.iImp->iFlags |= Bitmap::EExternal;
return ret;
} else {
ipeDebug(
"Could not load linked image at '%s' in '%s', resolved to '%s':\n%s\n%s\n%s",
pathAttr.z(), Platform::currentDirectory().z(), path.z(), errmsg, errpng,
errjpg);
Bitmap eret;
eret.init(attr);
eret.iImp->iPath = ret.iImp->iPath;
eret.iImp->iFlags = Bitmap::EExternal;
return eret;
}
}

void Bitmap::changeExternalPathRelativeBase(String new_base) {
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

To keep the code base consistent, I'd prefer camelCase: newBase instead of new_base.

new_base = Platform::realPath(new_base);
String old_path = Platform::realPath(externalPath());
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If we start using <filesystem>, should we just switch to use std::filesystem::absolute instead of Platform::realPath here?

It seems std::filesystem can relieve Ipe of the burden of all the special handling for Windows (where filenames have to be in wchar_t and the separator is a backslash).

setExternalPath(
std::filesystem::proximate(old_path.s(), new_base.s()).generic_string());
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I guess it should be generic_u8string. In Ipe, all strings are in UTF-8, even if that is not the computer's local encoding, and on Windows it will not work like this.

}

// --------------------------------------------------------------------
43 changes: 15 additions & 28 deletions src/ipelib/ipebitmap_unix.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -166,47 +166,35 @@ bool dctDecode(Buffer dctData, Buffer pixelData) {

// --------------------------------------------------------------------

//! Read PNG image from file.
/*! Returns the image as a Bitmap.
It will be compressed if \a deflate is set.
Sets \a dotsPerInch if the image file contains a resolution,
otherwise sets it to (0,0).
If reading the file fails, returns a null Bitmap,
and sets the error message \a errmsg.
*/
Bitmap Bitmap::readPNG(const char * fname, Vector & dotsPerInch, const char *& errmsg) {
const char * Bitmap::readPNGData(const char * fname, int & width, int & height,
uint32_t & flags, Buffer & pixels,
Vector & dotsPerInch) {
FILE * fp = Platform::fopen(fname, "rb");
if (!fp) {
errmsg = "Error opening file";
return Bitmap();
}
if (!fp) { return "Error opening file"; }

static const char pngerr[] = "PNG library error";
uint8_t header[8];
if (fread(header, 1, 8, fp) != 8 || png_sig_cmp(header, 0, 8)) {
errmsg = "The file does not appear to be a PNG image";
fclose(fp);
return Bitmap();
return "The file does not appear to be a PNG image";
}

png_structp png_ptr = png_create_read_struct(PNG_LIBPNG_VER_STRING,
(png_voidp) nullptr, nullptr, nullptr);
if (!png_ptr) {
errmsg = pngerr;
fclose(fp);
return Bitmap();
return pngerr;
}
png_infop info_ptr = png_create_info_struct(png_ptr);
if (!info_ptr) {
png_destroy_read_struct(&png_ptr, (png_infopp) nullptr, (png_infopp) nullptr);
errmsg = pngerr;
return Bitmap();
fclose(fp);
return pngerr;
}
if (setjmp(png_jmpbuf(png_ptr))) {
png_destroy_read_struct(&png_ptr, &info_ptr, (png_infopp) nullptr);
errmsg = pngerr;
fclose(fp);
return Bitmap();
return pngerr;
}

#if PNG_LIBPNG_VER >= 10504
Expand All @@ -217,8 +205,9 @@ Bitmap Bitmap::readPNG(const char * fname, Vector & dotsPerInch, const char *& e
png_set_sig_bytes(png_ptr, 8);
png_read_info(png_ptr, info_ptr);

int width = png_get_image_width(png_ptr, info_ptr);
int height = png_get_image_height(png_ptr, info_ptr);
width = png_get_image_width(png_ptr, info_ptr);
height = png_get_image_height(png_ptr, info_ptr);
flags = Bitmap::ERGB | Bitmap::EAlpha;
int color_type = png_get_color_type(png_ptr, info_ptr);

if (color_type == PNG_COLOR_TYPE_PALETTE) {
Expand Down Expand Up @@ -251,16 +240,15 @@ Bitmap Bitmap::readPNG(const char * fname, Vector & dotsPerInch, const char *& e
png_read_update_info(png_ptr, info_ptr);
if (png_get_bit_depth(png_ptr, info_ptr) != 8) {
png_destroy_read_struct(&png_ptr, &info_ptr, (png_infopp) nullptr);
errmsg = "Depth of PNG image is not eight bits.";
fclose(fp);
return Bitmap();
return "Depth of PNG image is not eight bits.";
}

const double mpi = 25.4 / 1000.0;
dotsPerInch = Vector(mpi * png_get_x_pixels_per_meter(png_ptr, info_ptr),
mpi * png_get_y_pixels_per_meter(png_ptr, info_ptr));

Buffer pixels(4 * width * height);
pixels = Buffer(4 * width * height);
std::vector<png_bytep> row(height);
for (int y = 0; y < height; ++y) row[y] = (png_bytep)pixels.data() + 4 * width * y;
png_read_image(png_ptr, row.data());
Expand All @@ -269,8 +257,7 @@ Bitmap Bitmap::readPNG(const char * fname, Vector & dotsPerInch, const char *& e
png_destroy_read_struct(&png_ptr, &info_ptr, (png_infopp) nullptr);
fclose(fp);

Bitmap bm(width, height, Bitmap::ERGB | Bitmap::EAlpha, pixels);
return bm;
return nullptr;
}

// --------------------------------------------------------------------
15 changes: 8 additions & 7 deletions src/ipelib/ipebitmap_win.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -87,19 +87,20 @@ bool dctDecode(Buffer dctData, Buffer pixelData) {
// --------------------------------------------------------------------
// The graphics file formats supported by GDI+ are
// BMP, GIF, JPEG, PNG, TIFF.
Bitmap Bitmap::readPNG(const char * fname, Vector & dotsPerInch, const char *& errmsg) {
const char * Bitmap::readPNGInfo(const char * fname, int & w, int & h, uint32_t & flags,
Buffer & pixels, Vector & dotsPerInch) {
// load without color correction
Gdiplus::Bitmap * bitmap = Gdiplus::Bitmap::FromFile(String(fname).w().data(), FALSE);
if (bitmap->GetLastStatus() != Gdiplus::Ok) {
delete bitmap;
return Bitmap();
return "GDI+ Status not OK";
}

dotsPerInch =
Vector(bitmap->GetHorizontalResolution(), bitmap->GetVerticalResolution());

int w = bitmap->GetWidth();
int h = bitmap->GetHeight();
w = bitmap->GetWidth();
h = bitmap->GetHeight();
flags = Bitmap::ENative;

Buffer pixelData(4 * w * h);
Gdiplus::BitmapData * bitmapData = new Gdiplus::BitmapData;
Expand All @@ -114,13 +115,13 @@ Bitmap Bitmap::readPNG(const char * fname, Vector & dotsPerInch, const char *& e
Gdiplus::ImageLockModeRead | Gdiplus::ImageLockModeUserInputBuf,
PixelFormat32bppARGB, bitmapData);

Bitmap bm(w, h, Bitmap::ENative, pixelData);
// TODO check whether we need to do the processing of analyze() here

bitmap->UnlockBits(bitmapData);
delete bitmapData;
delete bitmap;

return bm;
return nullptr;
}

// --------------------------------------------------------------------
Loading