Hello, In attach patch to add decoding of .psd file (Photoshop file).
PSD file can contain merge layers data at the end of the file (Image Data Section) This patch add support for this kind of files, using uncompress data Image Data Section Support Gray/A, and RGB/A with 8b or 16 bits by channel. Samples can be found here : https://we.tl/GK6yQhUV8j Comments welcome Martin
From a4b9c1373cc62d1bb38997704a924ecfe78ee4b4 Mon Sep 17 00:00:00 2001 From: Martin Vignali <martin.vign...@gmail.com> Date: Sat, 9 Jul 2016 15:59:41 +0200 Subject: [PATCH] libavcodec : add decoder for .psd image file. Decode the Image Data Section (who contain merge picture) Support RGB/A and Grayscale/A in 8bits and 16 bits by channel. Only support now, uncompress Image Data Section. --- Changelog | 2 +- doc/general.texi | 2 + libavcodec/Makefile | 1 + libavcodec/allcodecs.c | 1 + libavcodec/avcodec.h | 1 + libavcodec/codec_desc.c | 7 ++ libavcodec/psd.c | 310 +++++++++++++++++++++++++++++++++++++++++++++++ libavformat/Makefile | 1 + libavformat/allformats.c | 1 + libavformat/img2.c | 1 + libavformat/img2dec.c | 10 ++ libavformat/isom.c | 1 + 12 files changed, 337 insertions(+), 1 deletion(-) create mode 100644 libavcodec/psd.c diff --git a/Changelog b/Changelog index 99cdb80..c7b9908 100644 --- a/Changelog +++ b/Changelog @@ -2,7 +2,7 @@ Entries are sorted chronologically from oldest to youngest within each release, releases are sorted from youngest to oldest. version <next>: - +- Psd Decoder version 3.1: - DXVA2-accelerated HEVC Main10 decoding diff --git a/doc/general.texi b/doc/general.texi index 4db209f..06775f4 100644 --- a/doc/general.texi +++ b/doc/general.texi @@ -577,6 +577,8 @@ following image formats are supported: @item PNG @tab X @tab X @item PPM @tab X @tab X @tab Portable PixelMap image +@item PSD @tab @tab X + @tab Photoshop @item PTX @tab @tab X @tab V.Flash PTX format @item SGI @tab X @tab X diff --git a/libavcodec/Makefile b/libavcodec/Makefile index fd0d1f0..913b4a8 100644 --- a/libavcodec/Makefile +++ b/libavcodec/Makefile @@ -456,6 +456,7 @@ OBJS-$(CONFIG_PRORES_LGPL_DECODER) += proresdec_lgpl.o proresdsp.o proresdat OBJS-$(CONFIG_PRORES_ENCODER) += proresenc_anatoliy.o OBJS-$(CONFIG_PRORES_AW_ENCODER) += proresenc_anatoliy.o OBJS-$(CONFIG_PRORES_KS_ENCODER) += proresenc_kostya.o proresdata.o +OBJS-$(CONFIG_PSD_DECODER) += psd.o OBJS-$(CONFIG_PTX_DECODER) += ptx.o OBJS-$(CONFIG_QCELP_DECODER) += qcelpdec.o \ celp_filters.o acelp_vectors.o \ diff --git a/libavcodec/allcodecs.c b/libavcodec/allcodecs.c index 54efaad..458f603 100644 --- a/libavcodec/allcodecs.c +++ b/libavcodec/allcodecs.c @@ -278,6 +278,7 @@ void avcodec_register_all(void) REGISTER_ENCODER(PRORES_AW, prores_aw); REGISTER_ENCODER(PRORES_KS, prores_ks); REGISTER_DECODER(PRORES_LGPL, prores_lgpl); + REGISTER_DECODER(PSD, psd); REGISTER_DECODER(PTX, ptx); REGISTER_DECODER(QDRAW, qdraw); REGISTER_DECODER(QPEG, qpeg); diff --git a/libavcodec/avcodec.h b/libavcodec/avcodec.h index 39713ed..75a9970 100644 --- a/libavcodec/avcodec.h +++ b/libavcodec/avcodec.h @@ -409,6 +409,7 @@ enum AVCodecID { AV_CODEC_ID_MAGICYUV, AV_CODEC_ID_SHEERVIDEO, AV_CODEC_ID_YLC, + AV_CODEC_ID_PSD, /* various PCM "codecs" */ AV_CODEC_ID_FIRST_AUDIO = 0x10000, ///< A dummy id pointing at the start of audio codecs diff --git a/libavcodec/codec_desc.c b/libavcodec/codec_desc.c index 9d94b72..427553c 100644 --- a/libavcodec/codec_desc.c +++ b/libavcodec/codec_desc.c @@ -1425,6 +1425,13 @@ static const AVCodecDescriptor codec_descriptors[] = { .props = AV_CODEC_PROP_INTRA_ONLY | AV_CODEC_PROP_LOSSLESS, }, { + .id = AV_CODEC_ID_PSD, + .type = AVMEDIA_TYPE_VIDEO, + .name = "psd", + .long_name = NULL_IF_CONFIG_SMALL("Photoshop file"), + .props = AV_CODEC_PROP_INTRA_ONLY | AV_CODEC_PROP_LOSSLESS, + }, + { .id = AV_CODEC_ID_PTX, .type = AVMEDIA_TYPE_VIDEO, .name = "ptx", diff --git a/libavcodec/psd.c b/libavcodec/psd.c new file mode 100644 index 0000000..da9036f --- /dev/null +++ b/libavcodec/psd.c @@ -0,0 +1,310 @@ +/* + * Photoshop (.psd) image decoder + * Copyright (c) 2016 Jokyo Images + * + * This file is part of FFmpeg. + * + * FFmpeg is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * FFmpeg is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with FFmpeg; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + */ + +#include "bytestream.h" +#include "internal.h" + +enum PsdCompr { + PSD_RAW, + PSD_RLE, + PSD_ZIP_WITHOUT_P, + PSD_ZIP_WITH_P, +}; + +enum PsdColorMode { + PSD_BITMAP, + PSD_GRAYSCALE, + PSD_INDEXED, + PSD_RGB, + PSD_CMYK, + PSD_MULTICHANNEL, + PSD_DUOTONE, + PSD_LAB, +}; + +typedef struct PSDContext { + AVClass *class; + AVFrame *picture; + AVCodecContext *avctx; + GetByteContext gb; + + uint16_t channel_count; + uint16_t channel_depth; + + int width; + int height; + + enum PsdCompr compression; + enum PsdColorMode color_mode; +} PSDContext; + +static int decode_header(PSDContext * s) +{ + int signature, version, color_mode, len_section, compression; + int ret = 0; + + if (bytestream2_get_bytes_left(&s->gb) < 30) {/* File header section + color map data section length */ + av_log(s->avctx, AV_LOG_ERROR, "Header too short to parse.\n"); + return AVERROR_INVALIDDATA; + } + + signature = bytestream2_get_le32(&s->gb); + if (signature != 1397768760) { + av_log(s->avctx, AV_LOG_ERROR, "Wrong signature %d.\n", signature); + return AVERROR_INVALIDDATA; + } + + version = bytestream2_get_be16(&s->gb); + if (version != 1) { + av_log(s->avctx, AV_LOG_ERROR, "Wrong version %d.\n", version); + return AVERROR_INVALIDDATA; + } + + bytestream2_skip(&s->gb, 6);/* reserved */ + + s->channel_count = bytestream2_get_be16(&s->gb); + if ((s->channel_count < 1)||(s->channel_count > 56)) { + av_log(s->avctx, AV_LOG_ERROR, "Invalid channel count %d.\n", s->channel_count); + return AVERROR_INVALIDDATA; + } + + s->height = bytestream2_get_be32(&s->gb); + + if ((s->height < 1)||(s->height > 30000)) { + av_log(s->avctx, AV_LOG_ERROR, "Invalid s->height %d.\n", s->height); + return AVERROR_INVALIDDATA; + } + + s->width = bytestream2_get_be32(&s->gb); + if ((s->width < 1)||(s->width > 30000)) { + av_log(s->avctx, AV_LOG_ERROR, "Invalid s->width %d.\n", s->width); + return AVERROR_INVALIDDATA; + } + + if ((ret = ff_set_dimensions(s->avctx, s->width, s->height)) < 0) + return ret; + + s->channel_depth = bytestream2_get_be16(&s->gb); + + color_mode = bytestream2_get_be16(&s->gb); + switch (color_mode) { + case 0: + s->color_mode = PSD_BITMAP; + break; + case 1: + s->color_mode = PSD_GRAYSCALE; + break; + case 2: + s->color_mode = PSD_INDEXED; + break; + case 3: + s->color_mode = PSD_RGB; + break; + case 4: + s->color_mode = PSD_CMYK; + break; + case 7: + s->color_mode = PSD_MULTICHANNEL; + break; + case 8: + s->color_mode = PSD_DUOTONE; + break; + case 9: + s->color_mode = PSD_LAB; + break; + default: + av_log(s->avctx, AV_LOG_ERROR, "Unknown color mode %d.\n", color_mode); + return AVERROR_INVALIDDATA; + } + + /* color map data */ + len_section = bytestream2_get_be32(&s->gb); + if (bytestream2_get_bytes_left(&s->gb) < (len_section + 4)) { /* section and len next section */ + av_log(s->avctx, AV_LOG_ERROR, "Incomplete file.\n"); + return AVERROR_INVALIDDATA; + } + bytestream2_skip(&s->gb, len_section); + + /* image ressources */ + len_section = bytestream2_get_be32(&s->gb); + if (bytestream2_get_bytes_left(&s->gb) < (len_section + 4)) { /* section and len next section */ + av_log(s->avctx, AV_LOG_ERROR, "Incomplete file.\n"); + return AVERROR_INVALIDDATA; + } + bytestream2_skip(&s->gb, len_section); + + /* layers and masks */ + len_section = bytestream2_get_be32(&s->gb); + if (bytestream2_get_bytes_left(&s->gb) < len_section) { + av_log(s->avctx, AV_LOG_ERROR, "Incomplete file.\n"); + return AVERROR_INVALIDDATA; + } + bytestream2_skip(&s->gb, len_section); + + /* image section */ + if (bytestream2_get_bytes_left(&s->gb) < 2) { + av_log(s->avctx, AV_LOG_ERROR, "File without image data section.\n"); + return AVERROR_INVALIDDATA; + } + compression = bytestream2_get_be16(&s->gb); + + switch (compression) { + case 0: + s->compression = PSD_RAW; + av_log(s->avctx, AV_LOG_VERBOSE, "PSD_RAW COMPRESSION\n"); + break; + case 1: + s->compression = PSD_RLE; + avpriv_report_missing_feature(s->avctx, "RLE compression"); + return AVERROR_PATCHWELCOME; + break; + case 2: + s->compression = PSD_ZIP_WITHOUT_P; + avpriv_request_sample(s->avctx, "ZIP without predictor compression"); + return AVERROR_PATCHWELCOME; + break; + case 3: + s->compression = PSD_ZIP_WITH_P; + avpriv_request_sample(s->avctx, "ZIP with predictor compression"); + return AVERROR_PATCHWELCOME; + break; + default: + av_log(s->avctx, AV_LOG_ERROR, "Unknown compression %d.\n", compression); + return AVERROR_INVALIDDATA; + } + + return ret; +} + +static int decode_frame(AVCodecContext *avctx, void *data, + int *got_frame, AVPacket *avpkt) +{ + int ret; + int pixel_size;/* 1 for 8 bits, 2 for 16 bits */ + uint8_t *ptr; + int index_out, c, y, x, p; + + AVFrame *picture = data; + + PSDContext *s = avctx->priv_data; + s->avctx = avctx; + s->channel_count = 0; + s->channel_depth = 0; + + bytestream2_init(&s->gb, avpkt->data, avpkt->size); + + if ((ret = decode_header(s)) < 0) + return ret; + + pixel_size = s->channel_depth >> 3;/* in byte */ + + /* check length data for raw image data section */ + if (bytestream2_get_bytes_left(&s->gb) < (s->width * s->height * s->channel_count * pixel_size)) { + av_log(s->avctx, AV_LOG_ERROR, "Not enought data for raw image data section.\n"); + return AVERROR_INVALIDDATA; + } + + switch (s->color_mode) { + case PSD_RGB: + if (s->channel_count == 3) { + if (s->channel_depth == 8) { + avctx->pix_fmt = AV_PIX_FMT_RGB24; + } else if (s->channel_depth == 16) { + avctx->pix_fmt = AV_PIX_FMT_RGB48BE; + } else { + avpriv_report_missing_feature(avctx, "channel depth unsupported for rgb %d", s->channel_depth); + return AVERROR_PATCHWELCOME; + } + } else if (s->channel_count == 4) { + if (s->channel_depth == 8) { + avctx->pix_fmt = AV_PIX_FMT_RGBA; + } else if (s->channel_depth == 16) { + avctx->pix_fmt = AV_PIX_FMT_RGBA64BE; + } else { + avpriv_report_missing_feature(avctx, "channel depth unsupported for rgb %d", s->channel_depth); + return AVERROR_PATCHWELCOME; + } + } else { + avpriv_report_missing_feature(avctx, "channel count unsupported for rgb %d", s->channel_count); + return AVERROR_PATCHWELCOME; + } + break; + case PSD_GRAYSCALE: + if (s->channel_count == 1) { + if (s->channel_depth == 8) { + avctx->pix_fmt = AV_PIX_FMT_GRAY8; + } else if (s->channel_depth == 16) { + avctx->pix_fmt = AV_PIX_FMT_GRAY16BE; + } else { + avpriv_report_missing_feature(avctx, "channel depth unsupported for grayscale %d", s->channel_depth); + return AVERROR_PATCHWELCOME; + } + } else if (s->channel_count == 2) { + if (s->channel_depth == 8) { + avctx->pix_fmt = AV_PIX_FMT_YA8; + } else if (s->channel_depth == 16) { + avctx->pix_fmt = AV_PIX_FMT_YA16BE; + } else { + avpriv_report_missing_feature(avctx, "channel depth unsupported for grayscale %d", s->channel_depth); + return AVERROR_PATCHWELCOME; + } + } else { + avpriv_report_missing_feature(avctx, "channel count unsupported for grayscale %d", s->channel_count); + return AVERROR_PATCHWELCOME; + } + break; + default: + avpriv_report_missing_feature(avctx, "color mode unsupported %d", s->color_mode); + return AVERROR_PATCHWELCOME; + } + + if ((ret = ff_get_buffer(avctx, picture, 0)) < 0) + return ret; + + /* reorganize raw pixel. each channel is store one after this other */ + ptr = picture->data[0]; + + for (c = 0; c < s->channel_count; c++) { + for (y = 0; y < s->height; y++) { + for (x = 0; x < s->width; x++) { + index_out = y * s->width * pixel_size * s->channel_count + x * s->channel_count * pixel_size + c * pixel_size; + for (p = 0; p < pixel_size; p++) { + ptr[index_out + p] = bytestream2_get_byteu(&s->gb); + } + } + } + } + + picture->pict_type = AV_PICTURE_TYPE_I; + *got_frame = 1; + + return avpkt->size; +} + +AVCodec ff_psd_decoder = { + .name = "psd", + .long_name = NULL_IF_CONFIG_SMALL("Photoshop file"), + .type = AVMEDIA_TYPE_VIDEO, + .id = AV_CODEC_ID_PSD, + .priv_data_size = sizeof(PSDContext), + .decode = decode_frame, + .capabilities = AV_CODEC_CAP_DR1 | AV_CODEC_CAP_FRAME_THREADS, +}; diff --git a/libavformat/Makefile b/libavformat/Makefile index c49f9de..a79ca8f 100644 --- a/libavformat/Makefile +++ b/libavformat/Makefile @@ -234,6 +234,7 @@ OBJS-$(CONFIG_IMAGE_PGM_PIPE_DEMUXER) += img2dec.o img2.o OBJS-$(CONFIG_IMAGE_PICTOR_PIPE_DEMUXER) += img2dec.o img2.o OBJS-$(CONFIG_IMAGE_PNG_PIPE_DEMUXER) += img2dec.o img2.o OBJS-$(CONFIG_IMAGE_PPM_PIPE_DEMUXER) += img2dec.o img2.o +OBJS-$(CONFIG_IMAGE_PSD_PIPE_DEMUXER) += img2dec.o img2.o OBJS-$(CONFIG_IMAGE_QDRAW_PIPE_DEMUXER) += img2dec.o img2.o OBJS-$(CONFIG_IMAGE_SGI_PIPE_DEMUXER) += img2dec.o img2.o OBJS-$(CONFIG_IMAGE_SUNRAST_PIPE_DEMUXER) += img2dec.o img2.o diff --git a/libavformat/allformats.c b/libavformat/allformats.c index d490cc4..3d631e1 100644 --- a/libavformat/allformats.c +++ b/libavformat/allformats.c @@ -364,6 +364,7 @@ void av_register_all(void) REGISTER_DEMUXER (IMAGE_PICTOR_PIPE, image_pictor_pipe); REGISTER_DEMUXER (IMAGE_PNG_PIPE, image_png_pipe); REGISTER_DEMUXER (IMAGE_PPM_PIPE, image_ppm_pipe); + REGISTER_DEMUXER (IMAGE_PSD_PIPE, image_psd_pipe); REGISTER_DEMUXER (IMAGE_QDRAW_PIPE, image_qdraw_pipe); REGISTER_DEMUXER (IMAGE_SGI_PIPE, image_sgi_pipe); REGISTER_DEMUXER (IMAGE_SUNRAST_PIPE, image_sunrast_pipe); diff --git a/libavformat/img2.c b/libavformat/img2.c index 0f6f75c..a374eb5 100644 --- a/libavformat/img2.c +++ b/libavformat/img2.c @@ -73,6 +73,7 @@ const IdStrMap ff_img_tags[] = { { AV_CODEC_ID_DPX, "dpx" }, { AV_CODEC_ID_EXR, "exr" }, { AV_CODEC_ID_PICTOR, "pic" }, + { AV_CODEC_ID_PSD, "psd" }, { AV_CODEC_ID_V210X, "yuv10" }, { AV_CODEC_ID_WEBP, "webp" }, { AV_CODEC_ID_XBM, "xbm" }, diff --git a/libavformat/img2dec.c b/libavformat/img2dec.c index 9d6796f..a6a2148 100644 --- a/libavformat/img2dec.c +++ b/libavformat/img2dec.c @@ -821,6 +821,15 @@ static int png_probe(AVProbeData *p) return 0; } +static int psd_probe(AVProbeData *p) +{ + const uint8_t *b = p->buf; + + if (AV_RL32(b) == 1397768760) + return AVPROBE_SCORE_EXTENSION + 1; + return 0; +} + static int sgi_probe(AVProbeData *p) { const uint8_t *b = p->buf; @@ -946,6 +955,7 @@ IMAGEAUTO_DEMUXER(pgmyuv, AV_CODEC_ID_PGMYUV) IMAGEAUTO_DEMUXER(pictor, AV_CODEC_ID_PICTOR) IMAGEAUTO_DEMUXER(png, AV_CODEC_ID_PNG) IMAGEAUTO_DEMUXER(ppm, AV_CODEC_ID_PPM) +IMAGEAUTO_DEMUXER(psd, AV_CODEC_ID_PSD) IMAGEAUTO_DEMUXER(qdraw, AV_CODEC_ID_QDRAW) IMAGEAUTO_DEMUXER(sgi, AV_CODEC_ID_SGI) IMAGEAUTO_DEMUXER(sunrast, AV_CODEC_ID_SUNRAST) diff --git a/libavformat/isom.c b/libavformat/isom.c index d412f06..75fbf2f 100644 --- a/libavformat/isom.c +++ b/libavformat/isom.c @@ -254,6 +254,7 @@ const AVCodecTag ff_codec_movvideo_tags[] = { { AV_CODEC_ID_SGI, MKTAG('s', 'g', 'i', ' ') }, /* SGI */ { AV_CODEC_ID_DPX, MKTAG('d', 'p', 'x', ' ') }, /* DPX */ { AV_CODEC_ID_EXR, MKTAG('e', 'x', 'r', ' ') }, /* OpenEXR */ + { AV_CODEC_ID_EXR, MKTAG('p', 's', 'd', ' ') }, /* Psd */ { AV_CODEC_ID_PRORES, MKTAG('a', 'p', 'c', 'h') }, /* Apple ProRes 422 High Quality */ { AV_CODEC_ID_PRORES, MKTAG('a', 'p', 'c', 'n') }, /* Apple ProRes 422 Standard Definition */ -- 1.9.3 (Apple Git-50)
_______________________________________________ ffmpeg-devel mailing list ffmpeg-devel@ffmpeg.org http://ffmpeg.org/mailman/listinfo/ffmpeg-devel