Page 1 of 1

Multilayered PSD files

Posted: 2016-01-09T15:01:35-07:00
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 );
}

Re: Multilayered PSD files

Posted: 2016-01-09T16:29:19-07:00
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.