mirror of
https://github.com/aseprite/aseprite.git
synced 2024-10-04 13:59:46 +00:00
Initial to decode PSD files
This commit is contained in:
parent
ff851157cc
commit
9a12eb584b
3
.gitmodules
vendored
3
.gitmodules
vendored
@ -75,3 +75,6 @@
|
|||||||
[submodule "third_party/cityhash"]
|
[submodule "third_party/cityhash"]
|
||||||
path = third_party/cityhash
|
path = third_party/cityhash
|
||||||
url = https://github.com/aseprite/cityhash.git
|
url = https://github.com/aseprite/cityhash.git
|
||||||
|
[submodule "src/psd"]
|
||||||
|
path = src/psd
|
||||||
|
url = https://github.com/aseprite/psd.git
|
||||||
|
@ -102,6 +102,7 @@ add_subdirectory(doc)
|
|||||||
add_subdirectory(filters)
|
add_subdirectory(filters)
|
||||||
add_subdirectory(fixmath)
|
add_subdirectory(fixmath)
|
||||||
add_subdirectory(flic)
|
add_subdirectory(flic)
|
||||||
|
add_subdirectory(psd)
|
||||||
add_subdirectory(tga)
|
add_subdirectory(tga)
|
||||||
add_subdirectory(render)
|
add_subdirectory(render)
|
||||||
add_subdirectory(dio)
|
add_subdirectory(dio)
|
||||||
|
@ -132,6 +132,7 @@ set(file_formats
|
|||||||
file/jpeg_format.cpp
|
file/jpeg_format.cpp
|
||||||
file/pcx_format.cpp
|
file/pcx_format.cpp
|
||||||
file/png_format.cpp
|
file/png_format.cpp
|
||||||
|
file/psd_format.cpp
|
||||||
file/svg_format.cpp
|
file/svg_format.cpp
|
||||||
file/tga_format.cpp)
|
file/tga_format.cpp)
|
||||||
if(WITH_WEBP_SUPPORT)
|
if(WITH_WEBP_SUPPORT)
|
||||||
@ -676,6 +677,7 @@ target_link_libraries(app-lib
|
|||||||
render-lib
|
render-lib
|
||||||
laf-ft
|
laf-ft
|
||||||
laf-os
|
laf-os
|
||||||
|
psd
|
||||||
ui-lib
|
ui-lib
|
||||||
ver-lib
|
ver-lib
|
||||||
undo
|
undo
|
||||||
|
@ -1,4 +1,5 @@
|
|||||||
// Aseprite
|
// Aseprite
|
||||||
|
// Copyright (C) 2021 Igara Studio S.A.
|
||||||
// Copyright (C) 2001-2017 David Capello
|
// Copyright (C) 2001-2017 David Capello
|
||||||
//
|
//
|
||||||
// This program is distributed under the terms of
|
// This program is distributed under the terms of
|
||||||
@ -29,6 +30,7 @@ extern FileFormat* CreateIcoFormat();
|
|||||||
extern FileFormat* CreateJpegFormat();
|
extern FileFormat* CreateJpegFormat();
|
||||||
extern FileFormat* CreatePcxFormat();
|
extern FileFormat* CreatePcxFormat();
|
||||||
extern FileFormat* CreatePngFormat();
|
extern FileFormat* CreatePngFormat();
|
||||||
|
extern FileFormat* CreatePsdFormat();
|
||||||
extern FileFormat* CreateSvgFormat();
|
extern FileFormat* CreateSvgFormat();
|
||||||
extern FileFormat* CreateTgaFormat();
|
extern FileFormat* CreateTgaFormat();
|
||||||
|
|
||||||
@ -65,6 +67,7 @@ FileFormatsManager::FileFormatsManager()
|
|||||||
registerFormat(CreateJpegFormat());
|
registerFormat(CreateJpegFormat());
|
||||||
registerFormat(CreatePcxFormat());
|
registerFormat(CreatePcxFormat());
|
||||||
registerFormat(CreatePngFormat());
|
registerFormat(CreatePngFormat());
|
||||||
|
registerFormat(CreatePsdFormat());
|
||||||
registerFormat(CreateSvgFormat());
|
registerFormat(CreateSvgFormat());
|
||||||
registerFormat(CreateTgaFormat());
|
registerFormat(CreateTgaFormat());
|
||||||
|
|
||||||
|
394
src/app/file/psd_format.cpp
Normal file
394
src/app/file/psd_format.cpp
Normal file
@ -0,0 +1,394 @@
|
|||||||
|
// Aseprite
|
||||||
|
// Copyright (C) 2021 Igara Studio S.A.
|
||||||
|
//
|
||||||
|
// This program is distributed under the terms of
|
||||||
|
// the End-User License Agreement for Aseprite.
|
||||||
|
|
||||||
|
#include "app/file/file.h"
|
||||||
|
#include "app/file/file_format.h"
|
||||||
|
#include "base/file_handle.h"
|
||||||
|
#include "doc/blend_mode.h"
|
||||||
|
#include "doc/image_impl.h"
|
||||||
|
#include "doc/layer.h"
|
||||||
|
#include "doc/palette.h"
|
||||||
|
#include "doc/sprite.h"
|
||||||
|
#include "psd/psd.h"
|
||||||
|
|
||||||
|
namespace app {
|
||||||
|
|
||||||
|
doc::PixelFormat psd_cmode_to_ase_format(const psd::ColorMode mode)
|
||||||
|
{
|
||||||
|
switch (mode) {
|
||||||
|
case psd::ColorMode::Grayscale:
|
||||||
|
return doc::PixelFormat::IMAGE_GRAYSCALE;
|
||||||
|
case psd::ColorMode::RGB:
|
||||||
|
return doc::PixelFormat::IMAGE_RGB;
|
||||||
|
default:
|
||||||
|
return doc::PixelFormat::IMAGE_INDEXED;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
doc::BlendMode psd_blendmode_to_ase(const psd::LayerBlendMode mode)
|
||||||
|
{
|
||||||
|
switch (mode) {
|
||||||
|
case psd::LayerBlendMode::Multiply:
|
||||||
|
return doc::BlendMode::MULTIPLY;
|
||||||
|
case psd::LayerBlendMode::Darken:
|
||||||
|
return doc::BlendMode::DARKEN;
|
||||||
|
case psd::LayerBlendMode::ColorBurn:
|
||||||
|
return doc::BlendMode::COLOR_BURN;
|
||||||
|
case psd::LayerBlendMode::Lighten:
|
||||||
|
return doc::BlendMode::LIGHTEN;
|
||||||
|
case psd::LayerBlendMode::Screen:
|
||||||
|
return doc::BlendMode::SCREEN;
|
||||||
|
case psd::LayerBlendMode::ColorDodge:
|
||||||
|
return doc::BlendMode::COLOR_DODGE;
|
||||||
|
case psd::LayerBlendMode::Overlay:
|
||||||
|
return doc::BlendMode::OVERLAY;
|
||||||
|
case psd::LayerBlendMode::SoftLight:
|
||||||
|
return doc::BlendMode::SOFT_LIGHT;
|
||||||
|
case psd::LayerBlendMode::HardLight:
|
||||||
|
return doc::BlendMode::HARD_LIGHT;
|
||||||
|
case psd::LayerBlendMode::Difference:
|
||||||
|
return doc::BlendMode::DIFFERENCE;
|
||||||
|
case psd::LayerBlendMode::Exclusion:
|
||||||
|
return doc::BlendMode::EXCLUSION;
|
||||||
|
case psd::LayerBlendMode::Subtract:
|
||||||
|
return doc::BlendMode::SUBTRACT;
|
||||||
|
case psd::LayerBlendMode::Divide:
|
||||||
|
return doc::BlendMode::DIVIDE;
|
||||||
|
case psd::LayerBlendMode::Hue:
|
||||||
|
return doc::BlendMode::HSL_HUE;
|
||||||
|
case psd::LayerBlendMode::Saturation:
|
||||||
|
return doc::BlendMode::HSL_SATURATION;
|
||||||
|
case psd::LayerBlendMode::Color:
|
||||||
|
return doc::BlendMode::HSL_COLOR;
|
||||||
|
case psd::LayerBlendMode::Luminosity:
|
||||||
|
return doc::BlendMode::HSL_LUMINOSITY;
|
||||||
|
case psd::LayerBlendMode::Normal:
|
||||||
|
default:
|
||||||
|
return doc::BlendMode::NORMAL;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
class PsdFormat : public FileFormat {
|
||||||
|
const char* onGetName() const override { return "psd"; }
|
||||||
|
|
||||||
|
void onGetExtensions(base::paths& exts) const override
|
||||||
|
{
|
||||||
|
exts.push_back("psb");
|
||||||
|
exts.push_back("psd");
|
||||||
|
}
|
||||||
|
|
||||||
|
dio::FileFormat onGetDioFormat() const override
|
||||||
|
{
|
||||||
|
return dio::FileFormat::PSD_IMAGE;
|
||||||
|
}
|
||||||
|
|
||||||
|
int onGetFlags() const override { return FILE_SUPPORT_LOAD; }
|
||||||
|
|
||||||
|
bool onLoad(FileOp* fop) override;
|
||||||
|
bool onSave(FileOp* fop) override;
|
||||||
|
};
|
||||||
|
|
||||||
|
FileFormat* CreatePsdFormat()
|
||||||
|
{
|
||||||
|
return new PsdFormat();
|
||||||
|
}
|
||||||
|
|
||||||
|
class PsdDecoderDelegate : public psd::DecoderDelegate {
|
||||||
|
public:
|
||||||
|
PsdDecoderDelegate()
|
||||||
|
: m_currentImage(nullptr)
|
||||||
|
, m_currentLayer(nullptr)
|
||||||
|
, m_sprite(nullptr)
|
||||||
|
, m_pixelFormat(PixelFormat::IMAGE_INDEXED)
|
||||||
|
, m_layerHasTransparentChannel(false)
|
||||||
|
{ }
|
||||||
|
|
||||||
|
Sprite* getSprite() { return assembleDocument(); }
|
||||||
|
|
||||||
|
void onFileHeader(const psd::FileHeader& header) override
|
||||||
|
{
|
||||||
|
m_pixelFormat = psd_cmode_to_ase_format(header.colorMode);
|
||||||
|
m_sprite = new Sprite(
|
||||||
|
ImageSpec(ColorMode(m_pixelFormat), header.width, header.width));
|
||||||
|
m_sprite->setTotalFrames(frame_t(1));
|
||||||
|
}
|
||||||
|
|
||||||
|
// Emitted when a new layer has been chosen and its channel image data
|
||||||
|
// is about to be read
|
||||||
|
void onBeginLayer(const psd::LayerRecord& layerRecord) override
|
||||||
|
{
|
||||||
|
auto findIter = std::find_if(
|
||||||
|
m_layers.begin(), m_layers.end(), [&layerRecord](doc::Layer* layer) {
|
||||||
|
return layer->name() == layerRecord.name;
|
||||||
|
});
|
||||||
|
if (findIter == m_layers.end()) {
|
||||||
|
createNewLayer(layerRecord.name);
|
||||||
|
m_currentLayer->setVisible(layerRecord.isVisible());
|
||||||
|
m_layerHasTransparentChannel = hasTransparency(layerRecord.channels);
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
m_currentLayer = *findIter;
|
||||||
|
m_currentImage = m_currentLayer->cel(frame_t(0))->imageRef();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void onEndLayer(const psd::LayerRecord& layerRecord) override
|
||||||
|
{
|
||||||
|
ASSERT(m_currentLayer);
|
||||||
|
ASSERT(m_currentImage);
|
||||||
|
|
||||||
|
m_currentImage.reset();
|
||||||
|
m_currentLayer = nullptr;
|
||||||
|
m_layerHasTransparentChannel = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Emitted only if there's a palette in an image
|
||||||
|
void onColorModeData(const psd::ColorModeData& colorModeD) override
|
||||||
|
{
|
||||||
|
if (!colorModeD.colors.empty()) {
|
||||||
|
for (int i = 0; i < colorModeD.colors.size(); ++i) {
|
||||||
|
const psd::IndexColor iColor = colorModeD.colors[i];
|
||||||
|
m_palette.setEntry(i, rgba(iColor.r, iColor.g, iColor.b, 255));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Emitted when an image data is about to be transmitted
|
||||||
|
void onBeginImage(const psd::ImageData& imageData) override
|
||||||
|
{
|
||||||
|
if (!m_currentImage) {
|
||||||
|
// only occurs where there's an image with no layer
|
||||||
|
if (m_layers.empty()) {
|
||||||
|
createNewLayer("Layer 1");
|
||||||
|
m_layerHasTransparentChannel = hasTransparency(imageData.channels);
|
||||||
|
}
|
||||||
|
if (m_currentLayer) {
|
||||||
|
createNewImage(imageData.width, imageData.height);
|
||||||
|
linkNewCel(m_currentLayer, m_currentImage);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Emitted when all layers and their masks have been processed
|
||||||
|
void onLayersAndMask(const psd::LayersInformation& layersInfo) override
|
||||||
|
{
|
||||||
|
if (layersInfo.layers.size() == m_layers.size()) {
|
||||||
|
for (int i = 0; i < m_layers.size(); ++i) {
|
||||||
|
const psd::LayerRecord& layerRecord = layersInfo.layers[i];
|
||||||
|
|
||||||
|
LayerImage* layer = static_cast<LayerImage*>(m_layers[i]);
|
||||||
|
layer->setBlendMode(psd_blendmode_to_ase(layerRecord.blendMode));
|
||||||
|
|
||||||
|
Cel* cel = layer->cel(frame_t(0));
|
||||||
|
cel->setOpacity(layerRecord.opacity);
|
||||||
|
cel->setPosition(gfx::Point(layerRecord.left, layerRecord.top));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// This is called on images stored in raw data, most likely called when
|
||||||
|
// the first alpha channel contains the transparency data for the merged
|
||||||
|
// result. This channel is almost always stored in raw data format
|
||||||
|
void onImageScanline(const psd::ImageData& imgData,
|
||||||
|
const int y,
|
||||||
|
const psd::ChannelID chanID,
|
||||||
|
const std::vector<uint32_t>& data) override
|
||||||
|
{
|
||||||
|
if (!m_currentImage)
|
||||||
|
return;
|
||||||
|
|
||||||
|
for (int x = 0; x < data.size(); ++x) {
|
||||||
|
const color_t c = m_currentImage->getPixel(x, y);
|
||||||
|
const uint32_t pixel = normalizeValue(data[x], imgData.depth);
|
||||||
|
putPixel(x, y, c, pixel, chanID, m_pixelFormat);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// This method is called on images stored in RLE format
|
||||||
|
void onImageScanline(const psd::ImageData& img,
|
||||||
|
const int y,
|
||||||
|
const psd::ChannelID chanID,
|
||||||
|
const uint8_t* data,
|
||||||
|
const int bytes) override
|
||||||
|
{
|
||||||
|
if (!m_currentImage || y >= m_currentImage->height())
|
||||||
|
return;
|
||||||
|
|
||||||
|
for (int x = 0; x < bytes && x < m_currentImage->width(); ++x) {
|
||||||
|
const color_t c = m_currentImage->getPixel(x, y);
|
||||||
|
const uint32_t pixel = normalizeValue(data[x], img.depth);
|
||||||
|
putPixel(x, y, c, pixel, chanID, m_pixelFormat);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private:
|
||||||
|
// TODO: This is meant to convert values of a channel based on its depth
|
||||||
|
std::uint32_t normalizeValue(const uint32_t value, const int depth)
|
||||||
|
{
|
||||||
|
return value;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool hasTransparency(const std::vector<psd::ChannelID>& channelIDs)
|
||||||
|
{
|
||||||
|
return std::any_of(channelIDs.cbegin(),
|
||||||
|
channelIDs.cend(),
|
||||||
|
[](const psd::ChannelID& channelID) {
|
||||||
|
return channelID == psd::ChannelID::TransparencyMask ||
|
||||||
|
channelID == psd::ChannelID::Alpha;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
bool hasTransparency(const std::vector<psd::Channel>& channels)
|
||||||
|
{
|
||||||
|
return std::any_of(
|
||||||
|
channels.cbegin(), channels.cend(), [](const psd::Channel& channel) {
|
||||||
|
return channel.channelID == psd::ChannelID::TransparencyMask ||
|
||||||
|
channel.channelID == psd::ChannelID::Alpha;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
void linkNewCel(Layer* layer, doc::ImageRef image)
|
||||||
|
{
|
||||||
|
std::unique_ptr<Cel> cel(new doc::Cel(frame_t(0), image));
|
||||||
|
cel->setPosition(0, 0);
|
||||||
|
static_cast<LayerImage*>(layer)->addCel(cel.release());
|
||||||
|
}
|
||||||
|
|
||||||
|
void createNewLayer(const std::string& layerName)
|
||||||
|
{
|
||||||
|
m_currentLayer = new LayerImage(m_sprite);
|
||||||
|
m_sprite->root()->addLayer(m_currentLayer);
|
||||||
|
m_layers.push_back(m_currentLayer);
|
||||||
|
m_currentLayer->setName(layerName);
|
||||||
|
}
|
||||||
|
|
||||||
|
Sprite* assembleDocument()
|
||||||
|
{
|
||||||
|
if (m_palette.getModifications() > 1)
|
||||||
|
m_sprite->setPalette(&m_palette, true);
|
||||||
|
|
||||||
|
return m_sprite;
|
||||||
|
}
|
||||||
|
|
||||||
|
template<typename NewPixel>
|
||||||
|
void putPixel(const int x,
|
||||||
|
const int y,
|
||||||
|
const color_t prevPixelValue,
|
||||||
|
const NewPixel pixelValue,
|
||||||
|
const psd::ChannelID chanID,
|
||||||
|
const PixelFormat pixelFormat)
|
||||||
|
{
|
||||||
|
if (pixelFormat == doc::PixelFormat::IMAGE_RGB) {
|
||||||
|
int r = rgba_getr(prevPixelValue);
|
||||||
|
int g = rgba_getg(prevPixelValue);
|
||||||
|
int b = rgba_getb(prevPixelValue);
|
||||||
|
int a = m_layerHasTransparentChannel ? rgba_geta(prevPixelValue) : 255;
|
||||||
|
|
||||||
|
if (chanID == psd::ChannelID::Red) {
|
||||||
|
r = pixelValue;
|
||||||
|
}
|
||||||
|
else if (chanID == psd::ChannelID::Green) {
|
||||||
|
g = pixelValue;
|
||||||
|
}
|
||||||
|
else if (chanID == psd::ChannelID::Blue) {
|
||||||
|
b = pixelValue;
|
||||||
|
}
|
||||||
|
else if (chanID == psd::ChannelID::Alpha ||
|
||||||
|
chanID == psd::ChannelID::TransparencyMask) {
|
||||||
|
a = pixelValue;
|
||||||
|
}
|
||||||
|
m_currentImage->putPixel(x, y, rgba(r, g, b, a));
|
||||||
|
}
|
||||||
|
else if (pixelFormat == doc::PixelFormat::IMAGE_GRAYSCALE) {
|
||||||
|
int v = graya_getv(prevPixelValue);
|
||||||
|
int a = m_layerHasTransparentChannel ? graya_geta(prevPixelValue) : 255;
|
||||||
|
if (chanID == psd::ChannelID::Red) {
|
||||||
|
v = pixelValue;
|
||||||
|
}
|
||||||
|
else if (chanID == psd::ChannelID::Alpha ||
|
||||||
|
chanID == psd::ChannelID::TransparencyMask) {
|
||||||
|
a = pixelValue;
|
||||||
|
}
|
||||||
|
m_currentImage->putPixel(x, y, graya(v, a));
|
||||||
|
}
|
||||||
|
else if (pixelFormat == doc::PixelFormat::IMAGE_INDEXED) {
|
||||||
|
m_currentImage->putPixel(x, y, (color_t)pixelValue);
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
throw std::runtime_error(
|
||||||
|
"Only RGB/Grayscale/Indexed format is supported");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void createNewImage(const int width, const int height)
|
||||||
|
{
|
||||||
|
if (width <= 0 || height <= 0)
|
||||||
|
throw std::runtime_error("invalid image width/height");
|
||||||
|
|
||||||
|
m_currentImage.reset(Image::create(m_pixelFormat, width, height));
|
||||||
|
clear_image(m_currentImage.get(), 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
doc::ImageRef m_currentImage;
|
||||||
|
doc::Layer* m_currentLayer;
|
||||||
|
Sprite* m_sprite;
|
||||||
|
PixelFormat m_pixelFormat;
|
||||||
|
std::vector<doc::Layer*> m_layers;
|
||||||
|
Palette m_palette;
|
||||||
|
bool m_layerHasTransparentChannel;
|
||||||
|
};
|
||||||
|
|
||||||
|
bool PsdFormat::onLoad(FileOp* fop)
|
||||||
|
{
|
||||||
|
base::FileHandle fileHandle =
|
||||||
|
base::open_file_with_exception(fop->filename(), "rb");
|
||||||
|
FILE* f = fileHandle.get();
|
||||||
|
psd::StdioFileInterface fileInterface(f);
|
||||||
|
PsdDecoderDelegate pDelegate;
|
||||||
|
psd::Decoder decoder(&fileInterface, &pDelegate);
|
||||||
|
|
||||||
|
if (!decoder.readFileHeader()) {
|
||||||
|
fop->setError("The file doesn't have a valid PSD header\n");
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
const psd::FileHeader header = decoder.fileHeader();
|
||||||
|
|
||||||
|
if (header.colorMode != psd::ColorMode::RGB &&
|
||||||
|
header.colorMode != psd::ColorMode::Indexed &&
|
||||||
|
header.colorMode != psd::ColorMode::Grayscale) {
|
||||||
|
fop->setError("This preliminary work only supports "
|
||||||
|
"RGB, Grayscale & Indexed\n");
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
// This would be removed when support for 32bit per channel is supported
|
||||||
|
if (header.depth >= 32) {
|
||||||
|
fop->setError("Support for 32bit per channel isn't supported yet");
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
decoder.readColorModeData();
|
||||||
|
decoder.readImageResources();
|
||||||
|
decoder.readLayersAndMask();
|
||||||
|
decoder.readImageData();
|
||||||
|
}
|
||||||
|
catch (const std::runtime_error& e) {
|
||||||
|
fop->setError(e.what());
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
fop->createDocument(pDelegate.getSprite());
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool PsdFormat::onSave(FileOp* fop)
|
||||||
|
{
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
} // namespace app
|
@ -23,6 +23,7 @@
|
|||||||
#define PNG_MAGIC_DWORD2 0x0A1A0A0D
|
#define PNG_MAGIC_DWORD2 0x0A1A0A0D
|
||||||
#define WEBP_STAMP_1 "RIFF" // "RIFFnnnnWEBP"
|
#define WEBP_STAMP_1 "RIFF" // "RIFFnnnnWEBP"
|
||||||
#define WEBP_STAMP_2 "WEBP"
|
#define WEBP_STAMP_2 "WEBP"
|
||||||
|
#define PSD_STAMP "8BPS"
|
||||||
|
|
||||||
namespace dio {
|
namespace dio {
|
||||||
|
|
||||||
@ -65,6 +66,9 @@ FileFormat detect_format_by_file_content_bytes(const uint8_t* buf,
|
|||||||
std::strncmp((const char*)buf, GIF_89_STAMP, 6) == 0)
|
std::strncmp((const char*)buf, GIF_89_STAMP, 6) == 0)
|
||||||
return FileFormat::GIF_ANIMATION;
|
return FileFormat::GIF_ANIMATION;
|
||||||
|
|
||||||
|
if (std::strncmp((const char*)buf, PSD_STAMP, 4) == 0)
|
||||||
|
return FileFormat::PSD_IMAGE;
|
||||||
|
|
||||||
if (IS_MAGIC_WORD(4, ASE_MAGIC_NUMBER))
|
if (IS_MAGIC_WORD(4, ASE_MAGIC_NUMBER))
|
||||||
return FileFormat::ASE_ANIMATION;
|
return FileFormat::ASE_ANIMATION;
|
||||||
|
|
||||||
@ -156,6 +160,10 @@ FileFormat detect_format_by_file_extension(const std::string& filename)
|
|||||||
if (ext == "webp")
|
if (ext == "webp")
|
||||||
return FileFormat::WEBP_ANIMATION;
|
return FileFormat::WEBP_ANIMATION;
|
||||||
|
|
||||||
|
if (ext == "psd" ||
|
||||||
|
ext == "psb")
|
||||||
|
return FileFormat::PSD_IMAGE;
|
||||||
|
|
||||||
return FileFormat::UNKNOWN;
|
return FileFormat::UNKNOWN;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1,4 +1,5 @@
|
|||||||
// Aseprite Document IO Library
|
// Aseprite Document IO Library
|
||||||
|
// Copyright (c) 2021 Igara Studio S.A.
|
||||||
// Copyright (c) 2016-2017 David Capello
|
// Copyright (c) 2016-2017 David Capello
|
||||||
//
|
//
|
||||||
// This file is released under the terms of the MIT license.
|
// This file is released under the terms of the MIT license.
|
||||||
@ -32,6 +33,7 @@ enum class FileFormat {
|
|||||||
TARGA_IMAGE,
|
TARGA_IMAGE,
|
||||||
WEBP_ANIMATION,
|
WEBP_ANIMATION,
|
||||||
CSS_STYLE,
|
CSS_STYLE,
|
||||||
|
PSD_IMAGE,
|
||||||
};
|
};
|
||||||
|
|
||||||
} // namespace dio
|
} // namespace dio
|
||||||
|
1
src/psd
Submodule
1
src/psd
Submodule
@ -0,0 +1 @@
|
|||||||
|
Subproject commit 10acfc1c3439f5319fb013abd41f73f1892c98af
|
Loading…
Reference in New Issue
Block a user