Commit e891dace authored by Halla Rempt's avatar Halla Rempt
Browse files

Abuse the bmp filter for some other simple formats

BMP, GIF, XPM, XBM are file formats we don't want to spend much time on.
Krita is not for such files. So abuse the Qt support for those file formats
like we used to abuse GraphicsMagick or ImageMagick and that's it -- if
Qt cannot open these files, it's better to convert them using another
application before importing in Krita.

This also removes my gif filer which never worked really well.
BUG: 167713
BUG: 270634
BUG: 263987
parent 611800b6
......@@ -25,9 +25,6 @@ macro_log_feature( KDCRAW_FOUND "kdcraw" "The KDCraw library" "http://www.digika
macro_optional_find_package(OpenJPEG)
macro_log_feature(OPENJPEG_FOUND "openjpeg" "Free library for JPEG 2000 image compression" "http://www.openjpeg.org" FALSE "" "Required by the Krita JPEG 2000 filter")
macro_optional_find_package(GIF2)
macro_log_feature(GIF_FOUND "libungif/giflib" "Free library for reading and writing GIF images" "http://directory.fsf.org/project/libungif/" FALSE "" "Required by the Krita GIF filter")
include_directories(${KRITA_INCLUDES})
if(KDCRAW_FOUND)
......@@ -65,7 +62,3 @@ add_subdirectory(ppm)
add_subdirectory(xcf)
add_subdirectory(psd)
add_subdirectory(odg)
if(GIF_FOUND)
add_subdirectory(gif)
endif(GIF_FOUND)
......@@ -66,7 +66,7 @@ KoFilter::ConversionStatus KisBMPImport::convert(const QByteArray& from, const Q
{
dbgFile << "BMP import! From:" << from << ", To:" << to << "";
if (from != "image/bmp")
if (!(from == "image/bmp" || from == "image/x-xpixmap" || from == "image/gif" || from == "image/x-xbitmap"))
return KoFilter::NotImplemented;
if (to != "application/x-krita")
......
......@@ -62,7 +62,7 @@ Icon=krita
Categories=Qt;KDE;Office;Graphics;
StartupNotify=true
X-KDE-ServiceTypes=Calligra/Application
X-Calligra-DefaultMimeTypes=image/bmp
X-Calligra-DefaultMimeTypes=image/bmp,image/x-xpixmap,image/gif,image/x-xbitmap
X-DBUS-StartupType=Multi
X-DBUS-ServiceName=org.krita.krita
NoDisplay=true
......@@ -37,7 +37,7 @@ Name[wa]=Passete d' ebagaedje BMP di Krita
Name[x-test]=xxKrita BMP Export Filterxx
Name[zh_CN]=Krita BMP 导出过滤器
Name[zh_TW]=Krita BMP 匯出過濾程式
X-KDE-Export=image/bmp
X-KDE-Export=image/bmp,image/x-xpixmap,image/x-xbitmap
X-KDE-ServiceTypes=KOfficeFilter
Type=Service
X-KDE-Import=application/x-krita
......
......@@ -28,7 +28,7 @@ Name[zh_TW]=Krita BMP 匯入過濾程式
X-KDE-Export=application/x-krita
X-KDE-ServiceTypes=KOfficeFilter
Type=Service
X-KDE-Import=image/bmp
X-KDE-Import=image/bmp,image/x-xpixmap,image/gif,image/x-xbitmap
X-KDE-Weight=1
X-KDE-Library=kritabmpimport
NoDisplay=true
include_directories( ${KRITA_INCLUDES} ${GIF_INCLUDE_DIR})
set(libkritaconverter_LIB_SRCS
gif_converter.cpp
kis_gif_writer_visitor.cpp
)
set(kritagifimport_PART_SRCS
gif_import.cpp
${libkritaconverter_LIB_SRCS}
)
kde4_add_plugin(kritagifimport ${kritagifimport_PART_SRCS})
target_link_libraries(kritagifimport kritaui ${GIF_LIBRARIES})
install(TARGETS kritagifimport DESTINATION ${PLUGIN_INSTALL_DIR})
########### next target ###############
set(kritagifexport_PART_SRCS
gif_export.cpp
${libkritaconverter_LIB_SRCS}
)
kde4_add_plugin(kritagifexport ${kritagifexport_PART_SRCS})
target_link_libraries(kritagifexport kritaui ${GIF_LIBRARIES})
#install(TARGETS kritagifexport DESTINATION ${PLUGIN_INSTALL_DIR})
########### install files ###############
install( FILES krita_gif_import.desktop DESTINATION ${SERVICES_INSTALL_DIR})
#install( FILES krita_gif_export.desktop DESTINATION ${SERVICES_INSTALL_DIR})
install( PROGRAMS krita_gif.desktop DESTINATION ${XDG_APPS_INSTALL_DIR})
/*
* Copyright (c) 2009-2010 Boudewijn Rempt <boud@valdyas.org>
*
* 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.
*
* This program 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 General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*/
/*
* Inspired by qt-gif-plugin (http://gitorious.org/qt-gif-plugin)
* Copyright (C) 2009 Shawn T. Rutledge (shawn.t.rutledge@gmail.com)
*/
#include "gif_converter.h"
#include <QBitArray>
#include <QBuffer>
#include <QByteArray>
#include <QDataStream>
#include <QIODevice>
#include <kapplication.h>
#include <kio/netaccess.h>
#include <kio/deletejob.h>
#include <KoDocumentInfo.h>
#include <KoColorSpace.h>
#include <KoColorSpaceRegistry.h>
#include <KoColorSpaceTraits.h>
#include <kis_fill_painter.h>
#include <kis_types.h>
#include <kis_doc2.h>
#include <kis_image.h>
#include <kis_paint_layer.h>
#include <kis_undo_adapter.h>
#include <kis_random_accessor.h>
#include <kis_paint_device.h>
#include <kis_group_layer.h>
#include "kis_gif_writer_visitor.h"
static const int InterlacedOffset[] = {0, 4, 2, 1};
static const int InterlacedJumps[] = {8, 8, 4, 3};
int doInput(GifFileType* gif, GifByteType* data, int i)
{
QIODevice* in = (QIODevice*)gif->UserData;
return in->read((char*)data, i);
}
GifConverter::GifConverter(KisDoc2 *doc, KisUndoAdapter *adapter)
: m_transparentColorIndex(-1)
, m_doc(doc)
, m_img(0)
{
Q_UNUSED(adapter);
}
GifConverter::~GifConverter()
{
}
bool GifConverter::convertLine(GifFileType* gifFile, GifPixelType* line, int row, GifImageDesc &image, KisRandomAccessorPixel &accessor, KisPaintLayerSP layer) {
GifColorType color;
if (DGifGetLine(gifFile, line, image.Width) == GIF_ERROR) {
return false;
}
for (int col = 0; col < image.Width; ++col) {
accessor.moveTo(col + image.Left, row);
GifPixelType colorIndex = line[col];
if (image.ColorMap && colorIndex < image.ColorMap->ColorCount) {
color = image.ColorMap->Colors[colorIndex];
}
else if (gifFile->SColorMap && colorIndex < gifFile->SColorMap->ColorCount) {
color = gifFile->SColorMap->Colors[colorIndex];
}
else {
dbgFile << "color" << colorIndex << "not in any map";
color.Red = 255;
color.Green = 0;
color.Blue = 0;
}
quint8* dst = accessor.rawData();
KoRgbTraits<quint8>::setRed(dst, color.Red);
KoRgbTraits<quint8>::setGreen(dst, color.Green);
KoRgbTraits<quint8>::setBlue(dst, color.Blue);
if (colorIndex == m_transparentColorIndex) {
layer->colorSpace()->setOpacity(dst, OPACITY_TRANSPARENT_U8, 1);
}
else {
layer->colorSpace()->setOpacity(dst, OPACITY_OPAQUE_U8, 1);
}
}
return true;
}
KisNodeSP GifConverter::getNode(GifFileType* gifFile, KisImageWSP kisImage) {
if (DGifGetImageDesc(gifFile) == GIF_ERROR) {
warnFile << "Could not read gif image from file";
return 0;
}
KisPaintLayerSP layer = new KisPaintLayer(kisImage, kisImage->nextLayerName(), OPACITY_OPAQUE_U8);
GifImageDesc image = gifFile->Image;
layer->setX(image.Left);
layer->setY(image.Top);
dbgFile << "Creating layer w" << image.Width << "h" << image.Height << "left" << image.Left << "top" << image.Top;
Q_ASSERT(gifFile->SBackGroundColor < gifFile->SColorMap->ColorCount);
GifColorType color = gifFile->SColorMap->Colors[gifFile->SBackGroundColor];
quint8 fillPixel[4];
// always prefil the layer with transparency
KoRgbTraits<quint8>::setRed(fillPixel, color.Red);
KoRgbTraits<quint8>::setGreen(fillPixel, color.Green);
KoRgbTraits<quint8>::setBlue(fillPixel, color.Blue);
layer->colorSpace()->setOpacity(fillPixel, OPACITY_TRANSPARENT_U8, 1);
layer->paintDevice()->fill(0, 0, m_img->width(), m_img->height(), fillPixel);
GifPixelType* line = new GifPixelType[image.Width];
KisRandomAccessorPixel accessor = layer->paintDevice()->createRandomAccessor(image.Left, image.Top);
if (image.Interlace) {
for (int i = 0; i < 4; i++) {
for (int row = image.Top + InterlacedOffset[i]; row < image.Top + image.Height; row += InterlacedJumps[i]) {
convertLine(gifFile, line, row, image, accessor, layer);
}
}
} else {
for (int row = image.Top; row < image.Top + image.Height; ++ row) {
convertLine(gifFile, line, row, image, accessor, layer);
}
}
delete[] line;
dbgFile << "created node" << layer << layer->name();
return layer.data();
}
KisImageBuilder_Result GifConverter::decode(const KUrl& uri)
{
// open the file
QFile f(uri.toLocalFile());
if (!f.exists()) {
return KisImageBuilder_RESULT_NOT_EXIST;
}
if (!f.open(QIODevice::ReadOnly)) {
return KisImageBuilder_RESULT_FAILURE;
}
GifFileType* gifFile = DGifOpen(&f, doInput);
if (!gifFile) {
warnFile << "Could not read gif file" << uri;
return KisImageBuilder_RESULT_FAILURE;
}
dbgFile << "reading gif file" << uri;
// Creating the KisImageWSP
m_img = new KisImage(m_doc->undoAdapter(),
gifFile->SWidth,
gifFile->SHeight,
KoColorSpaceRegistry::instance()->rgb8(),
uri.toLocalFile());
Q_CHECK_PTR(m_img);
m_img->lock();
dbgFile << "image" << m_img << "width" << gifFile->SWidth << "height" << gifFile->SHeight;
GifRecordType recordType = UNDEFINED_RECORD_TYPE;
while (recordType != TERMINATE_RECORD_TYPE){
DGifGetRecordType(gifFile, &recordType);
switch (recordType) {
case IMAGE_DESC_RECORD_TYPE:
{
dbgFile << "reading IMAGE_DESC_RECORD_TYPE";
KisNodeSP node = getNode(gifFile, m_img);
if (!node) {
return KisImageBuilder_RESULT_FAILURE;
}
m_img->addNode(node);
}
break;
case EXTENSION_RECORD_TYPE:
{
dbgFile << "reading EXTENSION_RECORD_TYPE";
int extCode;
GifByteType* extData = 0;
if (DGifGetExtension(gifFile, &extCode, &extData) == GIF_ERROR) {
warnFile << "Error reading extension";
return KisImageBuilder_RESULT_FAILURE;
}
if (extData != 0) {
int len = extData[0];
dbgFile << "\tCode" << extCode << "length" << len << extData[0] << extData[1] << extData[2] << extData[3] << extData[4];
switch(extCode) {
case GRAPHICS_EXT_FUNC_CODE:
{
dbgFile << "\t GRAPHICS_EXT_FUNC_CODE";
// There are lots of fields, but only one that interests us, since we
// wouldn't store and save the animation information anyway.
if (extData[1] & 0x01)
{
m_transparentColorIndex = extData[4];
dbgFile << "\t\tTransparent color index" << m_transparentColorIndex;
}
}
break;
case COMMENT_EXT_FUNC_CODE:
{
dbgFile << "COMMENT_EXT_FUNC_CODE";
QByteArray comment((char*)(extData + 1), len);
m_doc->documentInfo()->setAboutInfo("comments", comment);
}
break;
case PLAINTEXT_EXT_FUNC_CODE:
dbgFile << "PLAINTEXT_EXT_FUNC_CODE";
// XXX: insert a monospace text shape layer? Nobody used this extension anyway...
break;
case APPLICATION_EXT_FUNC_CODE:
dbgFile << "APPLICATION_EXT_FUNC_CODE";
break;
}
}
}
break;
case TERMINATE_RECORD_TYPE:
dbgFile << "Reading TERMINATE_RECORD_TYPE";
break;
case UNDEFINED_RECORD_TYPE:
default:
{
warnFile << "Found an undefined record type:" << recordType;
}
}
}
m_img->unlock();
return KisImageBuilder_RESULT_OK;
}
KisImageBuilder_Result GifConverter::buildImage(const KUrl& uri)
{
if (uri.isEmpty())
return KisImageBuilder_RESULT_NO_URI;
if (!KIO::NetAccess::exists(uri, KIO::NetAccess::SourceSide, QApplication::activeWindow())) {
return KisImageBuilder_RESULT_NOT_EXIST;
}
// We're not set up to handle asynchronous loading at the moment.
KisImageBuilder_Result result = KisImageBuilder_RESULT_FAILURE;
QString tmpFile;
if (KIO::NetAccess::download(uri, tmpFile, qApp->activeWindow())) {
KUrl uriTF;
uriTF.setPath( tmpFile );
result = decode(uriTF);
KIO::NetAccess::removeTempFile(tmpFile);
}
return result;
}
KisImageWSP GifConverter::image()
{
return m_img;
}
int doOutput(GifFileType* gif, const GifByteType * data, int i)
{
QIODevice* out = (QIODevice*)gif->UserData;
return out->write((const char*)data, i);
}
int fillColorMap(const QImage& image, ColorMapObject& cmap) {
Q_ASSERT(image.format() == QImage::Format_Indexed8);
QVector<QRgb> colorTable = image.colorTable();
dbgFile << "Color table size" << colorTable.size();
Q_ASSERT(colorTable.size() <= 256);
// numColors must be a power of 2
int numColors = 1 << BitSize(image.numColors());
cmap.ColorCount = numColors;
cmap.BitsPerPixel = 8;
GifColorType* colorValues = (GifColorType*)malloc(cmap.ColorCount * sizeof(GifColorType));
cmap.Colors = colorValues;
int c = 0;
for(; c < image.numColors(); ++c)
{
colorValues[c].Red = qRed(colorTable[c]);
colorValues[c].Green = qGreen(colorTable[c]);
colorValues[c].Blue = qBlue(colorTable[c]);
}
// In case we had an actual number of colors that's not a power of 2,
// fill the rest with something (black perhaps).
for (; c < numColors; ++c)
{
colorValues[c].Red = 0;
colorValues[c].Green = 0;
colorValues[c].Blue = 0;
}
return numColors;
}
KisImageBuilder_Result GifConverter::buildFile(const KUrl& uri, KisImageWSP image)
{
if (!image)
return KisImageBuilder_RESULT_EMPTY;
if (uri.isEmpty())
return KisImageBuilder_RESULT_NO_URI;
if (!uri.isLocalFile())
return KisImageBuilder_RESULT_NOT_LOCAL;
// Open file for writing
QFile file(uri.toLocalFile());
if (!file.open(QIODevice::WriteOnly)) {
return (KisImageBuilder_RESULT_FAILURE);
}
m_img = image;
// get a list of all layers converted to 8 bit indexed QImages
KisGifWriterVisitor visitor;
m_img->rootLayer()->accept(visitor);
dbgFile << "Created" << visitor.m_layers.count() << "indexed layers";
// get a global colormap from the projection
QImage projection = m_img->projection()->convertToQImage(0).convertToFormat(QImage::Format_Indexed8);
ColorMapObject cmap;
int numColors = fillColorMap(projection, cmap);
dbgFile << "Filled colormap with" << numColors << "colors";
EGifSetGifVersion("89a");
GifFileType* gif = EGifOpen(&file, doOutput);
if (EGifPutScreenDesc(gif, m_img->width(), m_img->height(), numColors, 0, &cmap) == GIF_ERROR) {
dbgFile << "Failed to put the gif screen" << GifLastError();
return KisImageBuilder_RESULT_FAILURE;
}
dbgFile << "gif screen width" << m_img->width() << ", height" << m_img->height();
QString comments = m_doc->documentInfo()->aboutInfo("comments");
if (!comments.isEmpty()) {
dbgFile << "Comments:" << comments;
EGifPutComment(gif, comments.toAscii().constData());
}
// disposition for the frames go in packed fields here, but we only
// care that Qt puts the transparent color in the first entry in its
// colortable.
char extensionBlock[4];
extensionBlock[0] = 9;
extensionBlock[1] = 20;
extensionBlock[2] = 0;
extensionBlock[3] = 0;
EGifPutExtension(gif, GRAPHICS_EXT_FUNC_CODE, 4, &extensionBlock);
// now save all the layers as gif images.
foreach(const IndexedLayer layer, visitor.m_layers) {
ColorMapObject cmapLayer;
int numColorsLayer = fillColorMap(layer.image, cmapLayer);
#ifndef NDEBUG
Q_ASSERT(numColorsLayer <= 256);
#else
Q_UNUSED(numColorsLayer);
#endif
QRect rc(layer.topLeft, layer.image.size());
// Make sure the individual layers are not outside the gif screen bounds
rc = m_img->bounds().intersected(rc);
dbgFile << "layer size" << rc << "image bounds" << m_img->bounds();
if (EGifPutImageDesc(gif, rc.x(), rc.y(), rc.width(), rc.height(), 0, &cmapLayer) == GIF_ERROR) {
dbgFile << "Failed to add layer" << GifLastError();
return KisImageBuilder_RESULT_FAILURE;
}
int rowcount = rc.height();
int lineLength = rc.width();
dbgFile << "rows" << rowcount << "line length" << lineLength;
for (int row = 0; row < rowcount; ++row)
{
const uchar* line = layer.image.scanLine(row);
if (EGifPutLine(gif, (GifPixelType*)line, lineLength) == GIF_ERROR) {
dbgFile << "Failed to save scanline" << GifLastError() << "at row" << row;
return KisImageBuilder_RESULT_FAILURE;
}
}
}
EGifCloseFile(gif);
return KisImageBuilder_RESULT_OK;
}
void GifConverter::cancel()
{
m_stop = true;
}
#include "gif_converter.moc"
/*
* Copyright (c) 2009-2010 Boudewijn Rempt <boud@valdyas.org>
*
* 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.
*
* This program 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 General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*/
#ifndef _GIF_CONVERTER_H_
#define _GIF_CONVERTER_H_
#include <stdio.h>
#include <QObject>
#include <kio/job.h>
#include "kis_types.h"
#include <gif_lib.h>
class KisDoc2;
class KisUndoAdapter;
/**
* Image import/export plugins can use these results to report about success or failure.
*/
enum KisImageBuilder_Result {
KisImageBuilder_RESULT_FAILURE = -400,
KisImageBuilder_RESULT_NOT_EXIST = -300,
KisImageBuilder_RESULT_NOT_LOCAL = -200,
KisImageBuilder_RESULT_BAD_FETCH = -100,
KisImageBuilder_RESULT_INVALID_ARG = -50,
KisImageBuilder_RESULT_OK = 0,
KisImageBuilder_RESULT_PROGRESS = 1,
KisImageBuilder_RESULT_EMPTY = 100,
KisImageBuilder_RESULT_BUSY = 150,
KisImageBuilder_RESULT_NO_URI = 200,