2018-03-12 15:04:32 +00:00
|
|
|
#include "BitmapCache.hpp"
|
|
|
|
|
2019-04-04 07:20:11 +00:00
|
|
|
#include "libslic3r/Utils.hpp"
|
2020-01-31 15:50:11 +00:00
|
|
|
#include "../Utils/MacDarkMode.hpp"
|
2020-02-02 21:22:40 +00:00
|
|
|
#include "GUI.hpp"
|
|
|
|
|
2020-01-02 12:41:49 +00:00
|
|
|
#include <boost/filesystem.hpp>
|
2019-04-04 07:20:11 +00:00
|
|
|
|
2018-04-13 10:49:12 +00:00
|
|
|
#if ! defined(WIN32) && ! defined(__APPLE__)
|
|
|
|
#define BROKEN_ALPHA
|
|
|
|
#endif
|
|
|
|
|
|
|
|
#ifdef BROKEN_ALPHA
|
|
|
|
#include <wx/mstream.h>
|
|
|
|
#include <wx/rawbmp.h>
|
|
|
|
#endif /* BROKEN_ALPHA */
|
|
|
|
|
2019-04-04 07:20:11 +00:00
|
|
|
#define NANOSVG_IMPLEMENTATION
|
|
|
|
#include "nanosvg/nanosvg.h"
|
|
|
|
#define NANOSVGRAST_IMPLEMENTATION
|
|
|
|
#include "nanosvg/nanosvgrast.h"
|
2020-01-02 12:41:49 +00:00
|
|
|
//#include "GUI_App.hpp"
|
2019-04-04 07:20:11 +00:00
|
|
|
|
2018-03-12 15:04:32 +00:00
|
|
|
namespace Slic3r { namespace GUI {
|
|
|
|
|
2020-01-31 15:50:11 +00:00
|
|
|
BitmapCache::BitmapCache()
|
|
|
|
{
|
|
|
|
#ifdef __APPLE__
|
|
|
|
// Note: win->GetContentScaleFactor() is not used anymore here because it tends to
|
|
|
|
// return bogus results quite often (such as 1.0 on Retina or even 0.0).
|
|
|
|
// We're using the max scaling factor across all screens because it's very likely to be good enough.
|
|
|
|
m_scale = mac_max_scaling_factor();
|
|
|
|
#endif
|
|
|
|
}
|
|
|
|
|
2018-03-12 15:04:32 +00:00
|
|
|
void BitmapCache::clear()
|
|
|
|
{
|
|
|
|
for (std::pair<const std::string, wxBitmap*> &bitmap : m_map)
|
|
|
|
delete bitmap.second;
|
2019-04-10 07:56:32 +00:00
|
|
|
|
|
|
|
m_map.clear();
|
2018-03-12 15:04:32 +00:00
|
|
|
}
|
|
|
|
|
2019-04-05 16:53:02 +00:00
|
|
|
static wxBitmap wxImage_to_wxBitmap_with_alpha(wxImage &&image, float scale = 1.0f)
|
2018-04-13 10:49:12 +00:00
|
|
|
{
|
|
|
|
#ifdef BROKEN_ALPHA
|
|
|
|
wxMemoryOutputStream stream;
|
|
|
|
image.SaveFile(stream, wxBITMAP_TYPE_PNG);
|
|
|
|
wxStreamBuffer *buf = stream.GetOutputStreamBuffer();
|
|
|
|
return wxBitmap::NewFromPNGData(buf->GetBufferStart(), buf->GetBufferSize());
|
2019-04-05 16:53:02 +00:00
|
|
|
#else
|
|
|
|
#ifdef __APPLE__
|
|
|
|
// This is a c-tor native to Mac OS. We need to let the Mac OS wxBitmap implementation
|
|
|
|
// know that the image may already be scaled appropriately for Retina,
|
|
|
|
// and thereby that it's not supposed to upscale it.
|
|
|
|
// Contrary to intuition, the `scale` argument isn't "please scale this to such and such"
|
|
|
|
// but rather "the wxImage is sized for backing scale such and such".
|
|
|
|
return wxBitmap(std::move(image), -1, scale);
|
2018-04-13 10:49:12 +00:00
|
|
|
#else
|
|
|
|
return wxBitmap(std::move(image));
|
|
|
|
#endif
|
2019-04-05 16:53:02 +00:00
|
|
|
#endif
|
2018-04-13 10:49:12 +00:00
|
|
|
}
|
|
|
|
|
2020-01-31 15:50:11 +00:00
|
|
|
wxBitmap* BitmapCache::insert(const std::string &bitmap_key, size_t width, size_t height)
|
2018-03-12 15:04:32 +00:00
|
|
|
{
|
2018-04-13 10:49:12 +00:00
|
|
|
wxBitmap *bitmap = nullptr;
|
|
|
|
auto it = m_map.find(bitmap_key);
|
|
|
|
if (it == m_map.end()) {
|
|
|
|
bitmap = new wxBitmap(width, height);
|
2020-01-28 15:15:43 +00:00
|
|
|
#ifdef __APPLE__
|
|
|
|
// Contrary to intuition, the `scale` argument isn't "please scale this to such and such"
|
|
|
|
// but rather "the wxImage is sized for backing scale such and such".
|
|
|
|
// So, We need to let the Mac OS wxBitmap implementation
|
|
|
|
// know that the image may already be scaled appropriately for Retina,
|
|
|
|
// and thereby that it's not supposed to upscale it.
|
2020-01-31 15:50:11 +00:00
|
|
|
bitmap->CreateScaled(width, height, -1, m_scale);
|
2020-01-28 15:15:43 +00:00
|
|
|
#endif
|
2018-04-13 10:49:12 +00:00
|
|
|
m_map[bitmap_key] = bitmap;
|
|
|
|
} else {
|
|
|
|
bitmap = it->second;
|
2019-09-06 11:33:08 +00:00
|
|
|
if (size_t(bitmap->GetWidth()) != width || size_t(bitmap->GetHeight()) != height)
|
2018-04-13 10:49:12 +00:00
|
|
|
bitmap->Create(width, height);
|
|
|
|
}
|
|
|
|
#ifndef BROKEN_ALPHA
|
2018-03-12 15:04:32 +00:00
|
|
|
bitmap->UseAlpha();
|
|
|
|
#endif
|
|
|
|
return bitmap;
|
|
|
|
}
|
|
|
|
|
|
|
|
wxBitmap* BitmapCache::insert(const std::string &bitmap_key, const wxBitmap &bmp)
|
|
|
|
{
|
2018-04-13 10:49:12 +00:00
|
|
|
wxBitmap *bitmap = nullptr;
|
|
|
|
auto it = m_map.find(bitmap_key);
|
|
|
|
if (it == m_map.end()) {
|
|
|
|
bitmap = new wxBitmap(bmp);
|
|
|
|
m_map[bitmap_key] = bitmap;
|
|
|
|
} else {
|
|
|
|
bitmap = it->second;
|
|
|
|
*bitmap = bmp;
|
|
|
|
}
|
|
|
|
return bitmap;
|
2018-03-12 15:04:32 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
wxBitmap* BitmapCache::insert(const std::string &bitmap_key, const wxBitmap &bmp, const wxBitmap &bmp2)
|
|
|
|
{
|
2018-04-13 10:49:12 +00:00
|
|
|
// Copying the wxBitmaps is cheap as the bitmap's content is reference counted.
|
|
|
|
const wxBitmap bmps[2] = { bmp, bmp2 };
|
|
|
|
return this->insert(bitmap_key, bmps, bmps + 2);
|
2018-03-12 15:04:32 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
wxBitmap* BitmapCache::insert(const std::string &bitmap_key, const wxBitmap &bmp, const wxBitmap &bmp2, const wxBitmap &bmp3)
|
|
|
|
{
|
2018-04-13 10:49:12 +00:00
|
|
|
// Copying the wxBitmaps is cheap as the bitmap's content is reference counted.
|
|
|
|
const wxBitmap bmps[3] = { bmp, bmp2, bmp3 };
|
|
|
|
return this->insert(bitmap_key, bmps, bmps + 3);
|
2018-03-12 15:04:32 +00:00
|
|
|
}
|
|
|
|
|
2020-01-31 15:50:11 +00:00
|
|
|
wxBitmap* BitmapCache::insert(const std::string &bitmap_key, const wxBitmap *begin, const wxBitmap *end)
|
2018-03-12 15:04:32 +00:00
|
|
|
{
|
2018-04-13 10:49:12 +00:00
|
|
|
size_t width = 0;
|
|
|
|
size_t height = 0;
|
|
|
|
for (const wxBitmap *bmp = begin; bmp != end; ++ bmp) {
|
2020-01-31 15:50:11 +00:00
|
|
|
#ifdef __APPLE__
|
|
|
|
width += bmp->GetScaledWidth();
|
|
|
|
height = std::max<size_t>(height, bmp->GetScaledHeight());
|
|
|
|
#else
|
2018-04-13 10:49:12 +00:00
|
|
|
width += bmp->GetWidth();
|
|
|
|
height = std::max<size_t>(height, bmp->GetHeight());
|
2020-01-31 15:50:11 +00:00
|
|
|
#endif
|
2018-04-13 10:49:12 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
#ifdef BROKEN_ALPHA
|
2018-03-12 15:04:32 +00:00
|
|
|
|
2018-04-13 10:49:12 +00:00
|
|
|
wxImage image(width, height);
|
|
|
|
image.InitAlpha();
|
|
|
|
// Fill in with a white color.
|
|
|
|
memset(image.GetData(), 0x0ff, width * height * 3);
|
|
|
|
// Fill in with full transparency.
|
|
|
|
memset(image.GetAlpha(), 0, width * height);
|
|
|
|
size_t x = 0;
|
|
|
|
for (const wxBitmap *bmp = begin; bmp != end; ++ bmp) {
|
|
|
|
if (bmp->GetWidth() > 0) {
|
|
|
|
if (bmp->GetDepth() == 32) {
|
|
|
|
wxAlphaPixelData data(*const_cast<wxBitmap*>(bmp));
|
2018-09-19 16:54:23 +00:00
|
|
|
//FIXME The following method is missing from wxWidgets 3.1.1.
|
|
|
|
// It looks like the wxWidgets 3.0.3 called the wrapped bitmap's UseAlpha().
|
|
|
|
//data.UseAlpha();
|
2018-04-13 10:49:12 +00:00
|
|
|
if (data) {
|
|
|
|
for (int r = 0; r < bmp->GetHeight(); ++ r) {
|
|
|
|
wxAlphaPixelData::Iterator src(data);
|
|
|
|
src.Offset(data, 0, r);
|
|
|
|
unsigned char *dst_pixels = image.GetData() + (x + r * width) * 3;
|
|
|
|
unsigned char *dst_alpha = image.GetAlpha() + x + r * width;
|
|
|
|
for (int c = 0; c < bmp->GetWidth(); ++ c, ++ src) {
|
|
|
|
*dst_pixels ++ = src.Red();
|
|
|
|
*dst_pixels ++ = src.Green();
|
|
|
|
*dst_pixels ++ = src.Blue();
|
|
|
|
*dst_alpha ++ = src.Alpha();
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
} else if (bmp->GetDepth() == 24) {
|
|
|
|
wxNativePixelData data(*const_cast<wxBitmap*>(bmp));
|
|
|
|
if (data) {
|
|
|
|
for (int r = 0; r < bmp->GetHeight(); ++ r) {
|
|
|
|
wxNativePixelData::Iterator src(data);
|
|
|
|
src.Offset(data, 0, r);
|
|
|
|
unsigned char *dst_pixels = image.GetData() + (x + r * width) * 3;
|
|
|
|
unsigned char *dst_alpha = image.GetAlpha() + x + r * width;
|
|
|
|
for (int c = 0; c < bmp->GetWidth(); ++ c, ++ src) {
|
|
|
|
*dst_pixels ++ = src.Red();
|
|
|
|
*dst_pixels ++ = src.Green();
|
|
|
|
*dst_pixels ++ = src.Blue();
|
|
|
|
*dst_alpha ++ = wxALPHA_OPAQUE;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
x += bmp->GetWidth();
|
|
|
|
}
|
|
|
|
return this->insert(bitmap_key, wxImage_to_wxBitmap_with_alpha(std::move(image)));
|
|
|
|
|
|
|
|
#else
|
|
|
|
|
2020-01-31 15:50:11 +00:00
|
|
|
wxBitmap *bitmap = this->insert(bitmap_key, width, height);
|
2018-03-12 15:04:32 +00:00
|
|
|
wxMemoryDC memDC;
|
|
|
|
memDC.SelectObject(*bitmap);
|
2018-04-13 10:49:12 +00:00
|
|
|
memDC.SetBackground(*wxTRANSPARENT_BRUSH);
|
2018-03-12 15:04:32 +00:00
|
|
|
memDC.Clear();
|
|
|
|
size_t x = 0;
|
2018-04-13 10:49:12 +00:00
|
|
|
for (const wxBitmap *bmp = begin; bmp != end; ++ bmp) {
|
|
|
|
if (bmp->GetWidth() > 0)
|
|
|
|
memDC.DrawBitmap(*bmp, x, 0, true);
|
2020-01-31 15:50:11 +00:00
|
|
|
#ifdef __APPLE__
|
2020-01-28 15:15:43 +00:00
|
|
|
// we should "move" with step equal to non-scaled width
|
2020-01-31 15:50:11 +00:00
|
|
|
x += bmp->GetScaledWidth();
|
|
|
|
#else
|
|
|
|
x += bmp->GetWidth();
|
|
|
|
#endif
|
2018-04-13 10:49:12 +00:00
|
|
|
}
|
2018-03-12 15:04:32 +00:00
|
|
|
memDC.SelectObject(wxNullBitmap);
|
2018-04-13 10:49:12 +00:00
|
|
|
return bitmap;
|
2018-03-12 15:04:32 +00:00
|
|
|
|
2018-04-13 10:49:12 +00:00
|
|
|
#endif
|
2018-03-12 15:04:32 +00:00
|
|
|
}
|
|
|
|
|
2020-01-31 15:50:11 +00:00
|
|
|
wxBitmap* BitmapCache::insert_raw_rgba(const std::string &bitmap_key, unsigned width, unsigned height, const unsigned char *raw_data, const bool grayscale/* = false*/)
|
2019-04-04 07:20:11 +00:00
|
|
|
{
|
|
|
|
wxImage image(width, height);
|
|
|
|
image.InitAlpha();
|
|
|
|
unsigned char *rgb = image.GetData();
|
|
|
|
unsigned char *alpha = image.GetAlpha();
|
|
|
|
unsigned int pixels = width * height;
|
|
|
|
for (unsigned int i = 0; i < pixels; ++ i) {
|
|
|
|
*rgb ++ = *raw_data ++;
|
|
|
|
*rgb ++ = *raw_data ++;
|
|
|
|
*rgb ++ = *raw_data ++;
|
|
|
|
*alpha ++ = *raw_data ++;
|
|
|
|
}
|
2019-05-13 15:57:31 +00:00
|
|
|
|
|
|
|
if (grayscale)
|
2019-05-14 07:40:04 +00:00
|
|
|
image = image.ConvertToGreyscale(m_gs, m_gs, m_gs);
|
2019-05-13 15:57:31 +00:00
|
|
|
|
2020-01-31 15:50:11 +00:00
|
|
|
return this->insert(bitmap_key, wxImage_to_wxBitmap_with_alpha(std::move(image), m_scale));
|
2019-04-04 07:20:11 +00:00
|
|
|
}
|
|
|
|
|
2019-09-06 11:33:08 +00:00
|
|
|
wxBitmap* BitmapCache::load_png(const std::string &bitmap_name, unsigned width, unsigned height,
|
2019-05-13 15:57:31 +00:00
|
|
|
const bool grayscale/* = false*/)
|
2019-04-04 07:20:11 +00:00
|
|
|
{
|
2019-04-05 09:18:13 +00:00
|
|
|
std::string bitmap_key = bitmap_name + ( height !=0 ?
|
|
|
|
"-h" + std::to_string(height) :
|
2019-05-13 15:57:31 +00:00
|
|
|
"-w" + std::to_string(width))
|
|
|
|
+ (grayscale ? "-gs" : "");
|
2019-04-05 16:53:02 +00:00
|
|
|
|
2019-04-04 07:20:11 +00:00
|
|
|
auto it = m_map.find(bitmap_key);
|
|
|
|
if (it != m_map.end())
|
|
|
|
return it->second;
|
|
|
|
|
|
|
|
wxImage image;
|
|
|
|
if (! image.LoadFile(Slic3r::GUI::from_u8(Slic3r::var(bitmap_name + ".png")), wxBITMAP_TYPE_PNG) ||
|
|
|
|
image.GetWidth() == 0 || image.GetHeight() == 0)
|
|
|
|
return nullptr;
|
2019-04-05 09:18:13 +00:00
|
|
|
|
2019-09-06 11:33:08 +00:00
|
|
|
if (height != 0 && unsigned(image.GetHeight()) != height)
|
|
|
|
width = unsigned(0.5f + float(image.GetWidth()) * height / image.GetHeight());
|
|
|
|
else if (width != 0 && unsigned(image.GetWidth()) != width)
|
|
|
|
height = unsigned(0.5f + float(image.GetHeight()) * width / image.GetWidth());
|
2019-04-05 09:18:13 +00:00
|
|
|
|
|
|
|
if (height != 0 && width != 0)
|
|
|
|
image.Rescale(width, height, wxIMAGE_QUALITY_BILINEAR);
|
|
|
|
|
2019-05-13 15:57:31 +00:00
|
|
|
if (grayscale)
|
2019-05-14 07:40:04 +00:00
|
|
|
image = image.ConvertToGreyscale(m_gs, m_gs, m_gs);
|
2019-05-13 15:57:31 +00:00
|
|
|
|
2019-04-04 07:20:11 +00:00
|
|
|
return this->insert(bitmap_key, wxImage_to_wxBitmap_with_alpha(std::move(image)));
|
|
|
|
}
|
|
|
|
|
2019-05-13 15:57:31 +00:00
|
|
|
wxBitmap* BitmapCache::load_svg(const std::string &bitmap_name, unsigned target_width, unsigned target_height,
|
2020-01-31 15:50:11 +00:00
|
|
|
const bool grayscale/* = false*/, const bool dark_mode/* = false*/)
|
2019-04-04 07:20:11 +00:00
|
|
|
{
|
2020-01-07 15:17:20 +00:00
|
|
|
std::string bitmap_key = bitmap_name + ( target_height !=0 ?
|
|
|
|
"-h" + std::to_string(target_height) :
|
|
|
|
"-w" + std::to_string(target_width))
|
2020-01-31 15:50:11 +00:00
|
|
|
+ (m_scale != 1.0f ? "-s" + std::to_string(m_scale) : "")
|
2020-01-07 15:17:20 +00:00
|
|
|
+ (grayscale ? "-gs" : "");
|
|
|
|
|
2020-01-02 12:41:49 +00:00
|
|
|
/* For the Dark mode of any platform, we should draw icons in respect to OS background
|
|
|
|
* Note: All standard(regular) icons are collected in "icons" folder,
|
|
|
|
* SVG-icons, which have "Dark mode" variant, are collected in "icons/white" folder
|
|
|
|
*/
|
|
|
|
std::string folder;
|
|
|
|
if (dark_mode)
|
|
|
|
{
|
|
|
|
#ifdef __WXMSW__
|
|
|
|
folder = "white\\";
|
|
|
|
#else
|
|
|
|
folder = "white/";
|
|
|
|
#endif
|
2020-01-07 15:17:20 +00:00
|
|
|
auto it = m_map.find(folder + bitmap_key);
|
|
|
|
if (it != m_map.end())
|
|
|
|
return it->second;
|
|
|
|
else {
|
|
|
|
it = m_map.find(bitmap_key);
|
|
|
|
if (it != m_map.end())
|
|
|
|
return it->second;
|
|
|
|
}
|
|
|
|
|
2020-01-02 12:41:49 +00:00
|
|
|
if (!boost::filesystem::exists(Slic3r::var(folder + bitmap_name + ".svg")))
|
|
|
|
folder.clear();
|
2020-01-07 15:17:20 +00:00
|
|
|
else
|
|
|
|
bitmap_key = folder + bitmap_key;
|
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
|
|
|
auto it = m_map.find(bitmap_key);
|
|
|
|
if (it != m_map.end())
|
|
|
|
return it->second;
|
2020-01-02 12:41:49 +00:00
|
|
|
}
|
2019-04-04 07:20:11 +00:00
|
|
|
|
2020-01-02 12:41:49 +00:00
|
|
|
NSVGimage *image = ::nsvgParseFromFile(Slic3r::var(folder + bitmap_name + ".svg").c_str(), "px", 96.0f);
|
2019-04-04 07:20:11 +00:00
|
|
|
if (image == nullptr)
|
|
|
|
return nullptr;
|
|
|
|
|
2020-01-31 15:50:11 +00:00
|
|
|
target_height != 0 ? target_height *= m_scale : target_width *= m_scale;
|
2020-01-02 12:41:49 +00:00
|
|
|
|
2019-04-05 16:53:02 +00:00
|
|
|
float svg_scale = target_height != 0 ?
|
2019-04-05 09:18:13 +00:00
|
|
|
(float)target_height / image->height : target_width != 0 ?
|
|
|
|
(float)target_width / image->width : 1;
|
|
|
|
|
2019-04-05 16:53:02 +00:00
|
|
|
int width = (int)(svg_scale * image->width + 0.5f);
|
|
|
|
int height = (int)(svg_scale * image->height + 0.5f);
|
2019-04-04 07:20:11 +00:00
|
|
|
int n_pixels = width * height;
|
|
|
|
if (n_pixels <= 0) {
|
|
|
|
::nsvgDelete(image);
|
|
|
|
return nullptr;
|
|
|
|
}
|
|
|
|
|
|
|
|
NSVGrasterizer *rast = ::nsvgCreateRasterizer();
|
|
|
|
if (rast == nullptr) {
|
|
|
|
::nsvgDelete(image);
|
|
|
|
return nullptr;
|
|
|
|
}
|
|
|
|
|
|
|
|
std::vector<unsigned char> data(n_pixels * 4, 0);
|
2019-04-05 16:53:02 +00:00
|
|
|
::nsvgRasterize(rast, image, 0, 0, svg_scale, data.data(), width, height, width * 4);
|
2019-04-04 07:20:11 +00:00
|
|
|
::nsvgDeleteRasterizer(rast);
|
|
|
|
::nsvgDelete(image);
|
|
|
|
|
2020-01-31 15:50:11 +00:00
|
|
|
return this->insert_raw_rgba(bitmap_key, width, height, data.data(), grayscale);
|
2019-04-04 07:20:11 +00:00
|
|
|
}
|
|
|
|
|
2020-01-31 15:50:11 +00:00
|
|
|
//we make scaled solid bitmaps only for the cases, when its will be used with scaled SVG icon in one output bitmap
|
|
|
|
wxBitmap BitmapCache::mksolid(size_t width, size_t height, unsigned char r, unsigned char g, unsigned char b, unsigned char transparency, bool suppress_scaling/* = false*/)
|
2018-03-12 15:04:32 +00:00
|
|
|
{
|
2020-01-31 15:50:11 +00:00
|
|
|
double scale = suppress_scaling ? 1.0f : m_scale;
|
|
|
|
width *= scale;
|
|
|
|
height *= scale;
|
|
|
|
|
2018-03-12 15:04:32 +00:00
|
|
|
wxImage image(width, height);
|
|
|
|
image.InitAlpha();
|
|
|
|
unsigned char* imgdata = image.GetData();
|
|
|
|
unsigned char* imgalpha = image.GetAlpha();
|
|
|
|
for (size_t i = 0; i < width * height; ++ i) {
|
|
|
|
*imgdata ++ = r;
|
|
|
|
*imgdata ++ = g;
|
|
|
|
*imgdata ++ = b;
|
|
|
|
*imgalpha ++ = transparency;
|
|
|
|
}
|
2020-01-31 15:50:11 +00:00
|
|
|
return wxImage_to_wxBitmap_with_alpha(std::move(image), scale);
|
2018-03-12 15:04:32 +00:00
|
|
|
}
|
|
|
|
|
2020-02-02 21:22:40 +00:00
|
|
|
|
|
|
|
static inline int hex_digit_to_int(const char c)
|
|
|
|
{
|
|
|
|
return
|
|
|
|
(c >= '0' && c <= '9') ? int(c - '0') :
|
|
|
|
(c >= 'A' && c <= 'F') ? int(c - 'A') + 10 :
|
|
|
|
(c >= 'a' && c <= 'f') ? int(c - 'a') + 10 : -1;
|
|
|
|
}
|
|
|
|
|
|
|
|
bool BitmapCache::parse_color(const std::string& scolor, unsigned char* rgb_out)
|
|
|
|
{
|
|
|
|
rgb_out[0] = rgb_out[1] = rgb_out[2] = 0;
|
|
|
|
if (scolor.size() != 7 || scolor.front() != '#')
|
|
|
|
return false;
|
|
|
|
const char* c = scolor.data() + 1;
|
|
|
|
for (size_t i = 0; i < 3; ++i) {
|
|
|
|
int digit1 = hex_digit_to_int(*c++);
|
|
|
|
int digit2 = hex_digit_to_int(*c++);
|
|
|
|
if (digit1 == -1 || digit2 == -1)
|
|
|
|
return false;
|
|
|
|
rgb_out[i] = (unsigned char)(digit1 * 16 + digit2);
|
|
|
|
}
|
|
|
|
return true;
|
|
|
|
}
|
|
|
|
|
2018-03-12 15:04:32 +00:00
|
|
|
} // namespace GUI
|
|
|
|
} // namespace Slic3r
|