Multilayered PSD files

Questions and postings pertaining to the development of ImageMagick, feature enhancements, and ImageMagick internals. ImageMagick source code and algorithms are discussed here. Usage questions which are too arcane for the normal user list should also be posted here.
Post Reply
ggarra13
Posts: 30
Joined: 2015-04-17T14:08:07-07:00
Authentication code: 6789

Multilayered PSD files

Post by ggarra13 »

I am trying to create a multilayered PSD file from a multi part OpenEXR image. My code works fine when I save the image as a multilayer tiff or when I save a single layer in a PSD file. However, when I save a multilayered PSD file, all channels appear shifted or broken as if the stride was wrong. I believe this is a bug with ImageMagick (6.9.2-10) or with my usage of the api (more likely).
I post the relevant part of my source code in case someone spots an obvious problem (if someone has working code, it would be appreciated):

Code: Select all

    CMedia* p = const_cast< CMedia* >( this );

    const char* old_channel = channel();

    stringArray::const_iterator i;
    stringArray::const_iterator e;
    if ( opts->all_layers() )
    {
    	// Handle multiple layers
        i = p->layers().begin();
        e = p->layers().end();
    }
    else
    {
    	// Handle a single layer
        i = p->layers().begin();
        e = p->layers().end();
        for ( ; i != e; ++i )
        {
            if ( ( old_channel && *i == old_channel ) || *i == _("Color") )
            {
                e = i+1;
                break;
            }
        }
        if ( i == e )
        {
            i = p->layers().begin();
            e = i+1;
        }
    }


    Buffers bufs;

    std::string root = "ZXVCW#!";

    for ( ; i != e; ++i )
    {
        std::string x = *i;
        // std::cerr << "layer " << x << std::endl;

        if ( x == _("Lumma") || x == _("Alpha Overlay") ||
             x == _("Red") || x == _("Green") ||
             x == _("Blue") || x == _("Alpha") ||
             x == N_("RY") || x == N_("BY") ||
             x.find( _("anaglyph") ) != std::string::npos ||
             x.find( _("stereo") ) != std::string::npos )
        {
            continue;
        }

        std::string ext = x;
        
        size_t pos = ext.rfind( '.' );
        if ( pos != std::string::npos && pos != ext.size() )
        {
            ext = ext.substr( pos+1, ext.size() );
        }

        std::transform( ext.begin(), ext.end(), ext.begin(),
                        (int(*)(int)) toupper);

        if ( x.find(root) == 0 && root != "Z" ) continue;

        root = x;

        // This is the root layer
        if ( x == _("Color") ) x = "";


        p->channel( x.c_str() );

        mrv::image_type_ptr pic = hires();

        mrv::Recti daw = data_window();


        image_type::Format format = pic->format();

        bool  has_alpha = pic->has_alpha();


        bool must_convert = false;
        const char* channels;
        switch ( format )
        {
            case image_type::kRGB:
                channels = N_("RGB"); break;
            case image_type::kRGBA:
                channels = N_("RGBA"); break;
            case image_type::kBGRA:
                channels = N_("BGRA"); break;
            case image_type::kBGR:
                channels = N_("BGR"); break;
            case image_type::kLumma:
                channels = N_("I"); break;
            case image_type::kLummaA:
                channels = N_("IA"); break;
            default:
                must_convert = true;
                channels = N_("RGB");
                if ( has_alpha ) channels = N_("RGBA");
                break;
        }

        // std::cerr << "imagemagick channels " << channels 
        //           << " pic->channels " << pic->channels() << " alpha? "
        //           << has_alpha << std::endl;

        StorageType storage = CharPixel;
        switch( pic->pixel_type() )
        {
            case image_type::kShort:
                storage = ShortPixel;
                break;
            case image_type::kInt:
                storage = IntegerPixel;
                break;
            case image_type::kFloat:
                storage = FloatPixel;
                break;
            case image_type::kHalf:
                storage = ShortPixel;
                must_convert = true;
                break;
            case image_type::kByte:
            default:
                storage = CharPixel;
                break;
        }

        if ( o->pixel_type() != storage )
        {
            LOG_INFO( _("Original pixel type is ") 
                      << pixel_storage( storage )
                      << (".  Saving pixel type is ")
                      << pixel_storage( o->pixel_type() )
                      << "." );
            must_convert = true;
        }

        // if ( gamma() != 1.0 )
        //    must_convert = true;

        if ( opts->opengl() )
            must_convert = false;

       

        // Set matte (alpha)
        // MagickBooleanType matte = MagickFalse;
        // if ( has_alpha ) matte = MagickTrue;
        // MagickSetImageMatte( wand, matte );


        /**
         * Load image onto wand
         * 
         */
        boost::uint8_t* pixels = NULL;
        if ( must_convert )
        {
            unsigned pixel_size = 1;
            switch( o->pixel_type() )
            {
                case ShortPixel:
                    pixel_size = sizeof(short);
                    break;
                case IntegerPixel:
                    pixel_size = sizeof(int);
                    break;
                case FloatPixel:
                    pixel_size = sizeof(float);
                    break;
                case DoublePixel:
                    pixel_size = sizeof(double);
                    break;
                default:
                case CharPixel:
                    pixel_size = sizeof(char);
                    break;
            }

            unsigned data_size = width()*height()*pic->channels()*pixel_size;
            pixels = new boost::uint8_t[ data_size ];
            bufs.push_back( pixels );
        }
        else
        {
            pixels = (boost::uint8_t*)pic->data().get();
        }

        unsigned dw = pic->width();
        unsigned dh = pic->height();
        MagickWand* w = NewMagickWand();
        status = MagickConstituteImage( w, dw, dh, channels, 
                                        o->pixel_type(), pixels );
        if (status == MagickFalse)
        {
            destroyPixels(bufs);
            ThrowWandException( wand );
        }

        if ( !must_convert )
        {
            if ( pic->frame() == first_frame() )
            {
                LOG_INFO( _("No conversion needed.  Gamma: ") << _gamma );
            }
            MagickSetImageGamma( wand, _gamma );
        }
        else
        {
            if ( pic->frame() == first_frame() )
            {
                LOG_INFO( _("Conversion needed.  Gamma: 1.0") );
            }
            MagickSetImageGamma( wand, 1.0 );
        }

        if ( must_convert )
        {
            double one_gamma = 1.0 / _gamma;
            for ( unsigned y = 0; y < dh; ++y )
            {
                for ( unsigned x = 0; x < dw; ++x )
                {
                    //Note:  ImagePixel is always float regardless of what pic stores it as
                    ImagePixel p = pic->pixel( x, y );

                    if ( p.r > 0.f && isfinite(p.r) )
                        p.r = pow( p.r, one_gamma );
                    if ( p.g > 0.f && isfinite(p.g) )
                        p.g = pow( p.g, one_gamma );
                    if ( p.b > 0.f && isfinite(p.b) )
                        p.b = pow( p.b, one_gamma );

                    status = MagickImportImagePixels(w, x, y, 1, 1, channels, 
                                                     FloatPixel, &p[0] );
                    if (status == MagickFalse)
                    {
                        ThrowWandException( wand );
                    }

                }
            }


            if (status == MagickFalse)
            {
                destroyPixels(bufs);
                ThrowWandException( wand );
            }
        }
 
        if ( has_alpha )
        {
            status = MagickSetImageAlphaChannel( w, 
                                                 ActivateAlphaChannel );
            if ( status == MagickFalse )
            {
                ThrowWandException( wand );
            }
        }

        MagickSetLastIterator( wand );
        MagickSetImageCompression( wand, compression );
        MagickSetImageCompression( w, compression );
        std::string label = x;
        if ( label[0] == '#' )
        {
            pos = label.find( ' ' );
            if ( pos != std::string::npos && pos != label.size() )
            {
                label = label.substr( pos+1, label.size() );
            }
        }
        if ( label == "" )
        {
            MagickSetImageProperty( w, "label", NULL );
            // This is the Color channel, Add it as first channel
            MagickSetFirstIterator( wand ); 
        }
        else
        {
            MagickSetImageProperty( w, "label", label.c_str() );
        }

#if 1
        // Handle OpenEXR/PSD offsets
        Image* img = GetImageFromMagickWand( w );
        img->page.x = daw.x();
        img->page.y = daw.y();
        img->page.width = daw.w();
        img->page.height = daw.h();
#endif

        MagickAddImage( wand, w );


        DestroyMagickWand( w );
    }

    //
    // Store EXIF and IPTC data (if any)
    //

    /**
     * Write out image layer(s)
     * 
     */
    status = MagickWriteImages( wand, file, MagickTrue );
    if ( status == MagickFalse )
        ThrowWandException( wand );

    destroyPixels(bufs);
    DestroyMagickWand( wand );

    if (status == MagickFalse)
      {
	ThrowWandException( wand );
      }

    p->channel( old_channel );
}
ggarra13
Posts: 30
Joined: 2015-04-17T14:08:07-07:00
Authentication code: 6789

Re: Multilayered PSD files

Post by ggarra13 »

To answer my own question. What is needed is to set image->depth to a sensible value ( 8 or 16 ). This can be done with MagickSetImageDepth( wand, depth ). The PSD plugin requires it to be set, unlike the TIFF saver.
Post Reply