#pike __REAL_VERSION__ //! @appears Image.PSD //! PhotoShop Document image format. inherit Image._PSD; //! class Layer { string mode, name; //! int opacity; //! object image; //! object alpha; //! int flags; //! int xoffset, yoffset; //! int width, height; //! int mask_flags; //! int mask_xoffset, mask_yoffset; //! int mask_width, mask_height; //! int mask_default_color; //! } Layer decode_layer(mapping layer, mapping i) { // int stt = gethrtime(); Layer l = Layer(); int use_cmap; l->opacity = layer->opacity; l->width = layer->right-layer->left; l->height = layer->bottom-layer->top; l->xoffset = layer->left; l->yoffset = layer->top; l->image = Image.Image( l->width, l->height ); l->mode = layer->mode; l->flags = layer->flags; l->name = layer->name; l->mask_flags = layer->mask_flags; l->mask_default_color = layer->mask_default_color; l->mask_width = layer->mask_right-layer->mask_left; l->mask_height = layer->mask_bottom-layer->mask_top; l->mask_xoffset = layer->mask_left; l->mask_yoffset = layer->mask_top; if(l->mask_flags & 1) // pos relative to layer { l->mask_xoffset += l->xoffset; l->mask_yoffset += l->yoffset; } array colors; int inverted; switch(i->mode) { case RGB: array lays = ({}); foreach( layer->channels, mapping c ) { string mode; switch( (int)c->id ) { case 0: mode = "red"; break; case 1: mode = "green"; break; case 2: mode = "blue"; break; } if( mode ) { // int st = gethrtime(); if( !sizeof(lays) ) lays += ({ Image.Layer(___decode_image_channel(l->width, l->height, c->data)) }); else lays += (({ Image.Layer( ([ "image":___decode_image_channel(l->width, l->height, c->data), // "alpha_value":1.0, "mode":mode, ]) ) })); // werror(mode+" took %4.5f seconds\n", (gethrtime()-st)/1000000.0 ); c->data = 0; } } // int st = gethrtime(); l->image = Image.lay( lays )->image(); // werror("combine took %4.5f seconds\n", (gethrtime()-st)/1000000.0 ); break; case CMYK: inverted = 1; colors = ({ ({255,0,0,}), ({0,255,0,}), ({0,0,255,}), }) + ({ 255,255,255 }) * 24; l->image = Image.Image( l->width, l->height, 255, 255, 255); break; case Indexed: use_cmap = 1; break; default: werror("Unsupported layer format mode ("+i->mode+"), using greyscale\n"); case Greyscale: colors = ({ 255,255,255 })*24; break; } // int st = gethrtime(); foreach(layer->channels, mapping c) { object tmp; if( !colors && (c->id >= 0 )) continue; if( c->id != -2) tmp = ___decode_image_channel(l->width, l->height, c->data); else tmp = ___decode_image_channel(l->mask_width,l->mask_height,c->data); switch( c->id ) { default: if(!use_cmap) { if(inverted) l->image -= tmp*colors[c->id%sizeof(colors)]; else l->image += tmp*colors[c->id%sizeof(colors)]; } else { __apply_cmap( tmp, i->color_data ); l->image = tmp; } break; case -1: /* alpha */ if(!l->alpha) l->alpha = tmp; else l->alpha *= tmp; break; case -2: /* user mask */ if(!(l->mask_flags & 2)) /* layer mask disabled */ { array pad_color = ({ l->mask_default_color }) * 3; int x0 = l->xoffset - l->mask_xoffset; int y0 = l->yoffset - l->mask_yoffset; tmp = tmp->copy(x0, y0, x0 + l->image->xsize() - 1, y0 + l->image->ysize() - 1, @pad_color); if(l->mask_flags & 4) /* invert mask */ tmp = tmp->invert(); if(!l->alpha) l->alpha = tmp; else l->alpha *= tmp; break; } } c->data = 0; } // werror(" mode %s image %O alpha %O\n", // l->mode, l->image, l->alpha ); // werror("alpha/mask took %4.5f seconds\n", (gethrtime()-st)/1000000.0 ); // werror("TOTAL took %4.5f seconds\n\n", (gethrtime()-stt)/1000000.0 ); return l; } //! @decl mapping __decode(string|mapping data) //! //! Decodes a PSD image to a mapping, defined as follows. //! //! @mapping //! @member int(1..24) "channels" //! The number of channels in the image, including any alpha channels. //! @member int(1..30000) "height" //! @member int(1..30000) "width" //! The image dimensions. //! @member int(0..1) "compression" //! 1 if the image is compressed, 0 if not. //! @member int(1..1)|int(8..8)|int(16..16) "depth" //! The number of bits per channel. //! @member int(0..4)|int(7..9) "mode" //! The color mode of the file. //! @int //! @value 0 //! Bitmap //! @value 1 //! Greyscale //! @value 2 //! Indexed //! @value 3 //! RGB //! @value 4 //! CMYK //! @value 7 //! Multichannel //! @value 8 //! Duotone //! @value 9 //! Lab //! @endint //! @member string "color_data" //! Raw color data. //! @member string "image_data" //! Ram image data. //! @member mapping(string|int:mixed) "resources" //! Additional image data. See mappping below. //! @member array(Layer) "layers" //! An array with the layers of the image. See mapping below. //! @endmapping //! //! The resources mapping. Unknown resources will be identified //! by their ID number (as an int). //! @mapping //! @member string "caption" //! Image caption. //! @member string "url" //! Image associated URL. //! @member int "active_layer" //! Which layer is active. //! @member array(mapping(string:int)) "guides" //! An array with all guides stored in the image file. //! @mapping //! @member int "pos" //! The position of the guide. //! @member int(0..1) "vertical" //! 1 if the guide is vertical, 0 if it is horizontal. //! @endmapping //! @member mapping(string:int) "resinfo" //! Resolution information //! @mapping //! @member int "hres" //! @member int "hres_unit" //! @member int "width_unit" //! @member int "vres" //! @member int "vres_unit" //! @member int "height_unit" //! FIXME: Document these. //! @endmapping //! @endmapping //! //! The layer members: //! @mapping //! @member int "top" //! @member int "left" //! @member int "right" //! @member int "bottom" //! The rectangle containing the contents of the layer. //! @member int "mask_top" //! @member int "mask_left" //! @member int "mask_right" //! @member int "mask_bottom" //! @member int "mask_flags" //! FIXME: Document these //! @member int(0..255) "opacity" //! 0=transparent, 255=opaque. //! @member int "clipping" //! 0=base, 1=non-base. //! @member int "flags" //! bit 0=transparency protected //! bit 1=visible //! @member string "mode" //! Blend mode. //! @string //! @value "norm" //! Normal //! @value "dark" //! Darken //! @value "lite" //! Lighten //! @value "hue " //! Hue //! @value "sat " //! Saturation //! @value "colr" //! Color //! @value "lum " //! Luminosity //! @value "mul " //! Multiply //! @value "scrn" //! Screen //! @value "diss" //! Dissolve //! @value "over" //! Overlay //! @value "hLit" //! Hard light //! @value "sLit" //! Soft light //! @value "diff" //! Difference //! @endstring //! @member string "extra_data" //! Raw extra data. //! @member string "name" //! The name of the layer //! @member array(mapping(string:int|string)) "channels" //! The channels of the layer. Each array element is a //! mapping as follows //! @mapping //! @member int "id" //! The ID of the channel //! @int //! @value -2 //! User supplied layer mask //! @value -1 //! Transparency mask //! @value 0 //! Red //! @value 1 //! Green //! @value 2 //! Blue //! @endint //! @member string "data" //! The image data //! @endmapping //! @endmapping mapping __decode( mapping|string what, mapping|void options ) { mapping data; if(mappingp(what)) data = what; else data = ___decode( what ); what=0; array rl = ({}); foreach( data->layers, mapping l ) rl += ({ decode_layer( l, data ) }); data->layers = rl; return data; } array(object) decode_background( mapping data ) { object img; if( data->image_data ) img = ___decode_image_data(data->width, data->height, data->channels, data->mode, data->compression, data->image_data, data->color_data); return ({ img, 0 }); } //! Convert a photoshop mode name to pike @[Image.lay] mode names string translate_mode( string mode ) { switch( mode ) { case "norm": return "normal"; case "mul ": return "multiply"; case "add ": return "add"; case "diff": return "difference"; case "sub ": return "subtract"; case "diss": return "dissolve"; case "scrn": return "screen"; case "over": return "overlay"; case "dark": return "min"; case "lite": return "max"; case "hue ": return "hue"; case "div ": return "idivide"; case "hLit": return "hardlight"; case "colr": return "color"; // Not 100% like photoshop. case "lum ": return "value"; // Not 100% like photoshop. case "sat ": return "saturation";// Not 100% like photoshop. // Colorburn, not really multiply, but the best aproximation soo far. case "idiv": return "multiply"; // Exclusion. Not really difference, but very close. case "smud": return "difference"; // Soft light. Not really supported yet. For now, use hardlight with lower // opacity. Gives a rather good aproximation. case "sLit": return "hardlight"; default: werror("WARNING: PSD: Unsupported mode: "+mode+". Skipping layer\n"); return 0; } } //! @decl array(Image.Layer) decode_layers( string data, mapping|void options ) //! //! Decodes a PSD image to an array of Image.Layer objects //! //! The layer object have the following extra variables (to be queried //! using @[Image.Layer()->get_misc_value]): //! //! @string //! @value "image_guides" //! Returns array containing guide definitions. //! @value "name" //! Returns string containing the name of the layer. //! @value "visible" //! Is 1 of the layer is visible and 0 if it is hidden. //! @endstring //! //! Accepts these options: //! @mapping //! @member bool "draw_all_layers" //! If included, all layers will be decoded, even the non-visible ones. //! @member bool "crop_to_bounds" //! Remove areas that are outside the image boundaries in all layers //! @member Image.Color "background" //! If included, include a solid background layer with the given color //! @endmapping array decode_layers( string|mapping what, mapping|void opts ) { if(!opts) opts = ([]); if(!mappingp( what ) ) what = __decode( what ); mapping lopts = ([ "tiled":1, ]); if( opts->background ) { lopts->image = Image.Image( 32, 32, opts->background ); lopts->alpha = Image.Image( 32, 32, Image.Color.white ); lopts->alpha_value = 1.0; } object img, alpha; if( !what->layers || !sizeof(what->layers)) { [ img, alpha ] = decode_background( what ); if( img ) { lopts->image = img; if( alpha ) lopts->alpha = alpha; else lopts->alpha = 0; lopts->alpha_value = 1.0; } } array layers; Image.Layer lay; if( lopts->image ) { layers = ({ (lay=Image.Layer( lopts )) }); lay->set_misc_value( "visible", 1 ); lay->set_misc_value( "name", "Background" ); lay->set_misc_value( "image_guides", what->resources->guides ); } else layers = ({}); foreach(reverse(what->layers), object l) { if( !(l->flags & LAYER_FLAG_INVISIBLE) || opts->draw_all_layers ) { if( string m = translate_mode( l->mode ) ) { lay = Image.Layer( l->image, l->alpha, m ); lay->set_misc_value( "visible", !(l->flags & LAYER_FLAG_INVISIBLE) ); lay->set_misc_value( "name", l->name ); lay->set_misc_value( "image_guides", what->resources->guides ); if( l->mode == "sLit" ) l->opacity /= 3; l->image = 0; l->alpha = 0; if( l->opacity != 255 ) { float lo = l->opacity / 255.0; if( lay->alpha() ) lay->set_image( lay->image(), lay->alpha()*lo ); else lay->set_image( lay->image(), Image.Image( lay->xsize(), lay->yszize(), (int)(255*lo), (int)(255*lo), (int)(255*lo))); } if (opts->crop_to_bounds && lay->image()) { // Crop/expand this layer so it matches the image bounds. // This will lose data which extends beyond the image bounds // but keeps the image dimensions consistent. int x0 = -l->xoffset, y0 = -l->yoffset; int x1 = x0 + what->width - 1, y1 = y0 + what->height - 1; Image.Image new_img = lay->image()->copy(x0, y0, x1, y1); Image.Image new_alpha = lay->alpha() && lay->alpha()->copy(x0, y0, x1, y1); lay->set_image(new_img, new_alpha); } else lay->set_offset( l->xoffset, l->yoffset ); layers += ({ lay }); } } } // werror("%O\n", layers ); layers->set_misc_value( "size", ({ what->width, what->height }) ); return layers; } //! @decl mapping _decode(string|mapping data, mapping|void options) //! //! Decodes a PSD image to a mapping, with at least an //! 'image' and possibly an 'alpha' object. Data is either a PSD image, or //! a mapping (as received from @[__decode]) //! //! @param options //! @mapping //! @member array(int)|Image.Color "background" //! Sets the background to the given color. Arrays should be in //! the format ({r,g,b}). //! //! @member bool "draw_all_layers" //! Draw invisible layers as well. //! //! @member bool "draw_guides" //! Draw the guides. //! //! @member bool "draw_selection" //! Mark the selection using an overlay. //! //! @member bool "ignore_unknown_layer_modes" //! Do not asume 'Normal' for unknown layer modes. //! //! @member bool "mark_layers" //! Draw an outline around all (drawn) layers. //! //! @member Image.Font "mark_layer_names" //! Write the name of all layers using the font object, //! //! @member bool "mark_active_layer" //! Draw an outline around the active layer //! @endmapping //! //! @returns //! @mapping //! @member Image.Image "image" //! The image object. //! @member Image.Image "alpha" //! The alpha channel image object. //! @endmapping //! //! @note //! Throws upon error in data. For more information, see @[__decode] mapping _decode( string|mapping what, mapping|void opts ) { mapping data; if(!opts) opts = ([]); if(mappingp(what)) data = what; else data = __decode( what ); what=0; Image.Layer res = Image.lay(decode_layers( data, opts ), 0,0,data->width,data->height ); Image.Image img = res->image(); Image.Image alpha = res->alpha(); return ([ "image":img, "alpha":alpha, "type":"application/x-photoshop", ]); } //! @decl Image.Image decode(string data) //! Decodes a PSD image to a single image object. //! //! @note //! Throws upon error in data. To get access to more information like //! alpha channels and layer names, see @[_decode] and @[__decode]. Image.Image decode( string|mapping what, mapping|void opts ) { mapping data; if(!opts) opts = ([]); if(mappingp(what)) data = what; else data = __decode( what ); what=0; Image.Layer res = Image.lay(decode_layers( data, opts ), 0,0,data->width,data->height ); return res->image(); }