1=head1 NAME 2 3Prima::codecs - How to write a codec for Prima image subsystem 4 5=head1 DESCRIPTION 6 7How to write a codec for Prima image subsystem 8 9=head1 Start simple 10 11There are many graphical formats in the world, and yet more 12libraries, that depend on them. Writing a codec that supports 13particular library is a tedious task, especially if one wants many 14formats. Usually you never want to get into internal parts, the 15functionality comes first, and who needs all those funky options that 16format provides? We want to load a file and to show it. Everything 17else comes later - if ever. So, in a way to not scare you off, we 18start it simple. 19 20=head2 Load 21 22Define a callback function like: 23 24 static Bool 25 load( PImgCodec instance, PImgLoadFileInstance fi) 26 { 27 } 28 29Just that function is not enough for whole mechanism to work, 30but bindings will come later. Let us imagine we work with an imaginary 31library libduff, that we want to load files of .duf format. 32I<[ To discern imaginary code from real, imaginary will be prepended 33with _ - like, _libduff_loadfile ]>. So, we call _libduff_loadfile(), 34that loads black-and-white, 1-bits/pixel images, where 1 is white and 0 35is black. 36 37 38 static Bool 39 load( PImgCodec instance, PImgLoadFileInstance fi) 40 { 41 _LIBDUFF * _l = _libduff_load_file( fi-> fileName); 42 if ( !_l) return false; 43 44 // - create storage for our file 45 CImage( fi-> object)-> create_empty( fi-> object, 46 _l-> width, _l-> height, imBW); 47 48 // Prima wants images aligned to 4-bytes boundary, 49 // happily libduff has same considerations 50 memcpy( PImage( fi-> object)-> data, _l-> bits, 51 PImage( fi-> object)-> dataSize); 52 53 _libduff_close_file( _l); 54 55 return true; 56 } 57 58Prima keeps an open handle of the file; so we can use it if 59libduff trusts handles vs names: 60 61 { 62 _LIBDUFF * _l = _libduff_load_file_from_handle( fi-> f); 63 ... 64 // In both cases, you don't need to close the handle - 65 // however you might, it is ok: 66 67 _libduff_close_file( _l); 68 fclose( fi-> f); 69 // You just assign it to null to indicate that you've closed it 70 fi-> f = null; 71 ... 72 } 73 74Together with load() you have to implement minimal open_load() 75and close_load(). 76 77Simplest open_load() returns non-null pointer - it is enough to report 'o.k' 78 79 static void * 80 open_load( PImgCodec instance, PImgLoadFileInstance fi) 81 { 82 return (void*)1; 83 } 84 85Its result will be available in C<PImgLoadFileInstance-E<gt> instance>, 86just in case. If it was dynamically allocated, free it in close_load(). 87Dummy close_load() is doing simply nothing: 88 89 static void 90 close_load( PImgCodec instance, PImgLoadFileInstance fi) 91 { 92 } 93 94 95=head2 Writing to C<PImage-E<gt> data> 96 97As mentioned above, Prima insists on keeping its image data 98in 32-bit aligned scanlines. If libduff allows reading from 99file by scanlines, we can use this possibility as well: 100 101 102 PImage i = ( PImage) fi-> object; 103 // note - since this notation is more convenient than 104 // PImage( fi-> object)-> , instead i-> will be used 105 106 Byte * dest = i-> data + ( _l-> height - 1) * i-> lineSize; 107 while ( _l-> height--) { 108 _libduff_read_next_scanline( _l, dest); 109 dest -= i-> lineSize; 110 } 111 112Note that image is filled in reverse - Prima images are built 113like classical XY-coordinate grid, where Y ascends upwards. 114 115Here ends the simple part. You can skip down to 116L<"Registering with image subsystem"> part, if you want it fast. 117 118=head1 Single-frame loading 119 120=head2 Palette 121 122Our libduff can be black-and-white in two ways - 123where 0 is black and 1 is white and vice versa. While 1240B/1W is perfectly corresponding to imbpp1 | imGrayScale 125and no palette operations are needed ( Image cares 126automatically about these), 0W/1B is although black-and-white 127grayscale but should be treated like general imbpp1 type. 128 129 if ( l-> _reversed_BW) { 130 i-> palette[0].r = i-> palette[0].g = i-> palette[0].b = 0xff; 131 i-> palette[1].r = i-> palette[1].g = i-> palette[1].b = 0; 132 } 133 134NB. Image creates palette with size calculated by exponent of 2, since it can't know 135beforehand of the actual palette size. If color palette for, say, 4-bit 136image contains 15 of 16 possible for 4-bit image colors, code like 137 138 i-> palSize = 15; 139 140does the trick. 141 142=head2 Data conversion 143 144As mentioned before, Prima defines image scanline 145size to be aligned to 32 bits, and the formula for 146lineSize calculation is 147 148 lineSize = (( width * bits_per_pixel + 31) / 32) * 4; 149 150Prima defines number of converting routines between different 151data formats. Some of them can be applied to scanlines, and 152some to whole image ( due sampling algorithms ). These are 153defined in img_conv.h, and probably ones that you'll need 154would be C<bc_format1_format2>, which work on scanlines 155and probably ibc_repad, which combines some C<bc_XX_XX> with byte 156repadding. 157 158For those who are especially lucky, some libraries do not 159check between machine byte format and file byte format. 160Prima unfortunately doesn't provide easy method for determining 161this situation, but you have to convert your data in appropriate 162way to keep picture worthy of its name. Note the BYTEORDER symbol 163that is defined ( usually ) in sys/types.h 164 165=head2 Load with no data 166 167If a high-level code just needs image information rather than 168all its bits, codec can provide it in a smart way. Old code 169will work, but will eat memory and time. A flag 170C<PImgLoadFileInstance-E<gt> noImageData> is indicating if image data 171is needed. On that condition, codec needs to report only 172dimensions of the image - but the type must be set anyway. 173Here comes full code: 174 175 static Bool 176 load( PImgCodec instance, PImgLoadFileInstance fi) 177 { 178 _LIBDUFF * _l = _libduff_load_file( fi-> fileName); 179 HV * profile = fi-> frameProperties; 180 PImage i = ( PImage) fi-> frameProperties; 181 if ( !_l) return false; 182 183 CImage( fi-> object)-> create_empty( fi-> object, 1, 1, 184 _l-> _reversed_BW ? imbpp1 : imBW); 185 186 // copy palette, if any 187 if ( _l-> _reversed_BW) { 188 i-> palette[0].r = i-> palette[0].g = i-> palette[0].b = 0xff; 189 i-> palette[1].r = i-> palette[1].g = i-> palette[1].b = 0; 190 } 191 192 if ( fi-> noImageData) { 193 // report dimensions 194 pset_i( width, _l-> width); 195 pset_i( height, _l-> height); 196 return true; 197 } 198 199 // - create storage for our file 200 CImage( fi-> object)-> create_empty( fi-> object, 201 _l-> width, _l-> height, 202 _l-> _reversed_BW ? imbpp1 : imBW); 203 204 // Prima wants images aligned to 4-bytes boundary, 205 // happily libduff has same considerations 206 memcpy( PImage( fi-> object)-> data, _l-> bits, 207 PImage( fi-> object)-> dataSize); 208 209 210 _libduff_close_file( _l); 211 212 return true; 213 } 214 215The newly introduced macro C<pset_i> is a convenience operator, 216assigning integer (i) as a value to a hash key, given as a 217first parameter - it becomes string literal upon the 218expansion. Hash used for storage is a lexical of type C<HV*>. 219Code 220 221 HV * profile = fi-> frameProperties; 222 pset_i( width, _l-> width); 223 224is a prettier way for 225 226 hv_store( 227 fi-> frameProperties, 228 "width", strlen( "width"), 229 newSViv( _l-> width), 230 0); 231 232hv_store(), HV's and SV's along with other funny symbols are 233described in perlguts.pod in Perl installation. 234 235=head2 Return extra information 236 237Image attributes are dimensions, type, palette and data. 238However, it is only Prima point of view - different formats 239can supply number of extra information, often irrelevant but 240sometimes useful. From perl code, Image has a hash reference 'extras' 241on object, where comes all this stuff. Codec can report also 242such data, storing it in C<PImgLoadFileInstance-E<gt> frameProperties>. 243Data should be stored in native perl format, so if you're not 244familiar with perlguts, you better read it, especially if 245you want return arrays and hashes. But just in simple, you can 246return: 247 248=over 249 250=item 1 251 252integers: pset_i( integer, _l-E<gt> integer); 253 254=item 2 255 256floats: pset_f( float, _l-E<gt> float); 257 258=item 3 259 260strings: pset_c( string, _l-E<gt> charstar); 261- note - no malloc codec from you required 262 263=item 4 264 265prima objects: pset_H( Handle, _l-E<gt> primaHandle); 266 267=item 5 268 269SV's: pset_sv_noinc( scalar, newSVsv(sv)); 270 271=item 6 272 273hashes: pset_sv_noinc( scalar, ( SV *) newHV()); 274- hashes created through newHV() can be filled just in the same manner 275as described here 276 277=item 7 278 279arrays: pset_sv_noinc( scalar, ( SV *) newAV()); 280- arrays (AV) are described in perlguts also, but 281most useful function here is av_push. To push 4 values, 282for example, follow this code: 283 284 285 AV * av = newAV(); 286 for ( i = 0;i < 4;i++) av_push( av, newSViv( i)); 287 pset_sv_noinc( myarray, newRV_noinc(( SV *) av); 288 289is a C equivalent to 290 291 ->{extras}-> {myarray} = [0,1,2,3]; 292 293=back 294 295High level code can specify if the extra 296information should be loaded. This behavior is determined by 297flag C<PImgLoadFileInstance-E<gt> loadExtras>. Codec may skip this 298flag, the extra information will not be returned, even if 299C<PImgLoadFileInstance-E<gt> frameProperties> was changed. However, 300it is advisable to check for the flag, just for an efficiency. 301All keys, possibly assigned to frameProperties should 302be enumerated for high-level code. These strings should be 303represented into C<char ** PImgCodecInfo-E<gt> loadOutput> array. 304 305 static char * loadOutput[] = { 306 "hotSpotX", 307 "hotSpotY", 308 nil 309 }; 310 311 static ImgCodecInfo codec_info = { 312 ... 313 loadOutput 314 }; 315 316 static void * 317 init( PImgCodecInfo * info, void * param) 318 { 319 *info = &codec_info; 320 ... 321 } 322 323The code above is taken from codec_X11.c, where X11 bitmap can 324provide location of hot spot, two integers, X and Y. The type 325of the data is not specified. 326 327=head2 Loading to icons 328 329If high-level code wants an Icon instead of an Image, 330Prima takes care for producing and-mask automatically. 331However, if codec knows explicitly about transparency 332mask stored in a file, it might change object in the way 333it fits better. Mask is stored on Icon in a C<-E<gt> mask> field. 334 335a) Let us imagine, that 4-bit image always 336carries a transparent color index, in 0-15 range. In this case, 337following code will create desirable mask: 338 339 if ( kind_of( fi-> object, CIcon) && 340 ( _l-> transparent >= 0) && 341 ( _l-> transparent < PIcon( fi-> object)-> palSize)) { 342 PRGBColor p = PIcon( fi-> object)-> palette; 343 p += _l-> transparent; 344 PIcon( fi-> object)-> maskColor = ARGB( p->r, p-> g, p-> b); 345 PIcon( fi-> object)-> autoMasking = amMaskColor; 346 } 347 348Of course, 349 350 pset_i( transparentColorIndex, _l-> transparent); 351 352would be also helpful. 353 354b) if explicit bit mask is given, code will be like: 355 356 if ( kind_of( fi-> object, CIcon) && 357 ( _l-> maskData >= 0)) { 358 memcpy( PIcon( fi-> object)-> mask, _l-> maskData, _l-> maskSize); 359 PIcon( fi-> object)-> autoMasking = amNone; 360 } 361 362Note that mask is also subject to LSB/MSB and 32-bit alignment 363issues. Treat it as a regular imbpp1 data format. 364 365c) A format supports transparency information, but image does not 366contain any. In this case no action is required on the codec's part; 367the high-level code specifies if the transparency mask is created 368( iconUnmask field ). 369 370=head2 open_load() and close_load() 371 372open_load() and close_load() are used as brackets for load requests, 373and although they come to full power in multiframe load 374requests, it is very probable that correctly written 375codec should use them. Codec that assigns C<false> to 376C<PImgCodecInfo-E<gt> canLoadMultiple> claims that it cannot load 377those images that have index different from zero. It may 378report total amount of frames, but still be incapable of 379loading them. 380There is also a load sequence, called null-load, 381when no load() calls are made, just open_load() and close_load(). 382These requests are made in case codec can provide some file 383information without loading frames at all. It can be any 384information, of whatever kind. It have to be stored into the hash 385C<PImgLoadFileInstance-E<gt> fileProperties>, to be filled once on 386open_load(). The only exception is C<PImgLoadFileInstance-E<gt> frameCount>, 387which can be filled on open_load(). Actually, frameCount could be 388filled on any load stage, except close_load(), to make sense in 389frame positioning. Even single frame codec is advised to fill 390this field, at least to tell whether file is empty ( frameCount == 0) or 391not ( frameCount == 1). More about frameCount comes into chapters 392dedicated to multiframe requests. 393For strictly single-frame codecs it is therefore advised 394to care for open_load() and close_load(). 395 396=head2 Load input 397 398So far codec is expected to respond for noImageData 399hint only, and it is possible to allow a high-level code to alter 400codec load behavior, passing specific parameters. 401C<PImgLoadFileInstance-E<gt> profile> is a hash, that contains these 402parameters. The data that should be applied to all frames and/or 403image file are set there when open_load() is called. These data, 404plus frame-specific keys passed to every load() call. 405However, Prima passes only those hash keys, which are 406returned by load_defaults() function. This functions returns newly 407created ( by calling newHV()) hash, with accepted keys and their 408default ( and always valid ) value pairs. 409Example below defines speed_vs_memory integer value, that 410should be 0, 1 or 2. 411 412 static HV * 413 load_defaults( PImgCodec c) 414 { 415 HV * profile = newHV(); 416 pset_i( speed_vs_memory, 1); 417 return profile; 418 } 419 ... 420 static Bool 421 load( PImgCodec instance, PImgLoadFileInstance fi) 422 { 423 ... 424 HV * profile = fi-> profile; 425 if ( pexist( speed_vs_memory)) { 426 int speed_vs_memory = pget_i( speed_vs_memory); 427 if ( speed_vs_memory < 0 || speed_vs_memory > 2) { 428 strcpy( fi-> errbuf, "speed_vs_memory should be 0, 1 or 2"); 429 return false; 430 } 431 _libduff_set_load_optimization( speed_vs_memory); 432 } 433 } 434 435The latter code chunk can be applied to open_load() as well. 436 437=head2 Returning an error 438 439Image subsystem defines no severity gradation for codec errors. 440If error occurs during load, codec returns false value, which 441is C<null> on open_load() and C<false> on load. It is advisable to 442explain the error, otherwise the user gets just "Loading error" 443string. To do so, error message is to be copied to 444C<PImgLoadFileInstance-E<gt> errbuf>, which is C<char[256]>. 445On an extreme severe error codec may call croak(), 446which jumps to the closest G_EVAL block. If there is no G_EVAL 447blocks then program aborts. This condition could also happen if 448codec calls some Prima code that issues croak(). This condition 449is untrappable, - at least without calling perl functions. 450Understanding that that behavior is not acceptable, 451it is still under design. 452 453=head1 Multiple-frame load 454 455In order to indicate that a codec is ready to read 456multiframe images, it must set C<PImgCodecInfo-E<gt> canLoadMultiple> 457flag to true. This only means, that codec should respond to the 458C<PImgLoadFileInstance-E<gt> frame> field, which is integer that 459can be in range from C<0> to C<PImgLoadFileInstance-E<gt> frameCount - 1>. 460It is advised that codec should change the frameCount from 461its original value C<-1> to actual one, to help Prima filter range 462requests before they go down to the codec. The only real problem that 463may happen to the codec which it strongly unwilling to initialize 464frameCount, is as follows. 465If a loadAll request was made ( corresponding boolean 466C<PImgLoadFileInstance-E<gt> loadAll> flag is set for codec's information) 467and frameCount is not initialized, then Prima starts loading all frames, 468incrementing frame index until it receives an error. Assuming the 469first error it gets is an EOF, it reports no error, so there's no 470way for a high-level code to tell whether there was an loading error or 471an end-of-file condition. 472Codec may initialize frameCount at any time during open_load() 473or load(), even together with false return value. 474 475=head1 Saving 476 477Approach for handling saving requests is very similar to a load ones. 478For the same reason and with same restrictions functions save_defaults() 479open_save(), save() and close_save() are defined. Below shown a 480typical saving code and highlighted differences from load. 481As an example we'll take existing codec_X11.c, which 482defines extra hot spot coordinates, x and y. 483 484 485 static HV * 486 save_defaults( PImgCodec c) 487 { 488 HV * profile = newHV(); 489 pset_i( hotSpotX, 0); 490 pset_i( hotSpotY, 0); 491 return profile; 492 } 493 494 static void * 495 open_save( PImgCodec instance, PImgSaveFileInstance fi) 496 { 497 return (void*)1; 498 } 499 500 static Bool 501 save( PImgCodec instance, PImgSaveFileInstance fi) 502 { 503 PImage i = ( PImage) fi-> object; 504 Byte * l; 505 ... 506 507 fprintf( fi-> f, "#define %s_width %d\n", name, i-> w); 508 fprintf( fi-> f, "#define %s_height %d\n", name, i-> h); 509 if ( pexist( hotSpotX)) 510 fprintf( fi-> f, "#define %s_x_hot %d\n", name, (int)pget_i( hotSpotX)); 511 if ( pexist( hotSpotY)) 512 fprintf( fi-> f, "#define %s_y_hot %d\n", name, (int)pget_i( hotSpotY)); 513 fprintf( fi-> f, "static char %s_bits[] = {\n ", name); 514 ... 515 // printing of data bytes is omitted 516 } 517 518 static void 519 close_save( PImgCodec instance, PImgSaveFileInstance fi) 520 { 521 } 522 523Save request takes into account defined supported types, that 524are defined in C<PImgCodecInfo-E<gt> saveTypes>. Prima converts image 525to be saved into one of these formats, before actual save() call 526takes place. 527Another boolean flag, C<PImgSaveFileInstance-E<gt> append> 528is summoned to govern appending to or rewriting a file, but 529this functionality is under design. Its current value 530is a hint, if true, for a codec not to rewrite but rather 531append the frames to an existing file. Due to increased 532complexity of the code, that should respond to the append hint, 533this behavior is not required. 534 535Codec may set two of PImgCodecInfo flags, canSave and 536canSaveMultiple. Save requests will never be called if canSave 537is false, and append requests along with multiframe save requests 538would be never invoked for a codec with canSaveMultiple set to false. 539Scenario for a multiframe save request is the same as for a load one. All the 540issues concerning palette, data converting and saving extra 541information are actual, however there's no corresponding flag like 542loadExtras - codec is expected to save all information what it can extract from 543C<PImgSaveFileInstance-E<gt> objectExtras> hash. 544 545 546=head1 Registering with image subsystem 547 548Finally, the code have to be registered. It is not as illustrative 549but this part better not to be oversimplified. 550A codec's callback functions are set into ImgCodecVMT structure. 551Those function slots that are unused should not be defined as 552dummies - those are already defined and gathered under struct 553CNullImgCodecVMT. That's why all functions in the illustration code 554were defined as static. 555A codec have to provide some information that Prima 556uses to decide what codec should load this particular file. 557If no explicit directions given, Prima asks those codecs whose 558file extensions match to file's. 559init() should return pointer to the filled struct, that describes 560codec's capabilities: 561 562 // extensions to file - might be several, of course, thanks to dos... 563 static char * myext[] = { "duf", "duff", nil }; 564 565 // we can work only with 1-bit/pixel 566 static int mybpp[] = { 567 imbpp1 | imGrayScale, // 1st item is a default type 568 imbpp1, 569 0 }; // Zero means end-of-list. No type has zero value. 570 571 // main structure 572 static ImgCodecInfo codec_info = { 573 "DUFF", // codec name 574 "Numb & Number, Inc.", // vendor 575 _LIBDUFF_VERS_MAJ, _LIBDUFF_VERS_MIN, // version 576 myext, // extension 577 "DUmb Format", // file type 578 "DUFF", // file short type 579 nil, // features 580 "", // module 581 true, // canLoad 582 false, // canLoadMultiple 583 false, // canSave 584 false, // canSaveMultiple 585 mybpp, // save types 586 nil, // load output 587 }; 588 589 static void * 590 init( PImgCodecInfo * info, void * param) 591 { 592 *info = &codec_info; 593 return (void*)1; // just non-null, to indicate success 594 } 595 596The result of init() is stored into C<PImgCodec-E<gt> instance>, and 597info into C<PImgCodec-E<gt> info>. If dynamic memory was allocated 598for these structs, it can be freed on done() invocation. 599Finally, the function that is invoked from Prima, 600is the only that required to be exported, is responsible for 601registering a codec: 602 603 void 604 apc_img_codec_duff( void ) 605 { 606 struct ImgCodecVMT vmt; 607 memcpy( &vmt, &CNullImgCodecVMT, sizeof( CNullImgCodecVMT)); 608 vmt. init = init; 609 vmt. open_load = open_load; 610 vmt. load = load; 611 vmt. close_load = close_load; 612 apc_img_register( &vmt, nil); 613 } 614 615This procedure can register as many codecs as it wants to, 616but currently Prima is designed so that one codec_XX.c file 617should be connected to one library only. 618 619The name of the procedure is apc_img_codec_ plus 620library name, that is required for a compilation with Prima. 621File with the codec should be called codec_duff.c ( is our case) 622and put into img directory in Prima source tree. Following 623these rules, Prima will be assembled with libduff.a ( or duff.lib, 624or whatever, the actual library name is system dependent) - if the library is present. 625 626 627=head1 AUTHOR 628 629Dmitry Karasik, E<lt>dmitry@karasik.eu.orgE<gt>. 630 631=head1 SEE ALSO 632 633L<Prima>, L<Prima::Image>, L<Prima::internals>, L<Prima::image-load> 634