
tutorial
-----------------------------

  this tutorial intends to generally explain basic terms of image
  processing and to instruct the programming of render.library.



introduction
-----------------------------

  render.library provides low-level image processing. there are no
  functions implemented that interact with higher-level resources,
  such as rastports or datatypes. you may call render.library an
  image processing kernel.

  there's no reason for hesitation, though. i hope this tutorial
  will help you to find the thread to image processing and
  conversion. as you will see, the functions provided with
  render.library are very easy to use, once you've understood the
  basic terms, and most functions provide much more power and
  functionality than it might appear at first sight.



philosophy
-----------------------------

  function interfaces are kept lean and logical. first of all i
  focused on using as few definitions as possible. you will notice
  that you can use the majority of render.library's functions
  without setting up a single structure. each function was reduced
  to an absolute minimum of mandatory parameters. any argument that
  in some way could be assigned a default value to has been made
  optional and was banned to the taglists.

  render.library is layouted quasi object-oriented. everything is
  kept in black boxes, and objects heavily interact internally, only
  you don't know anything about the chemistry. many functions may be
  interpreted as constructors, destructors and methods, only the
  interfaces are implemented procedurally. there is nothing
  available for misguiding, insane assumptions.


overview
-----------------------------

  render.library covers the following domains:

    > planar to chunky conversion

      and vice versa. the functions in this domain are considered
      low-level, and they do not interact with higher-level
      graphics.library instances such as rastports. you have to
      take good care not to play havoc with non-standard bitmaps.
      please read the appropriate autodoc chapters very carefully.

      - Planar2ChunkyA()
      - Chunky2BitMapA()


    > chunky to truecolor conversion

      this is a simple task, and render.library serves it with
      high speed and flexibility. Amiga-specific modes (HAM6 and
      HAM8) are correctly handled even with horizontal offsets.
        
      - Chunky2RGBA()


    > palettes
    
      render.library uses a special type of color-lookup tables,
      called 'palettes'. a render.library palette is much more
      than a mere table of RGB values. all that nasty stuff is
      hidden from you throughout these functions:

      - CreatePaletteA()
      - ImportPaletteA()
      - ExportPaletteA()
      - SortPaletteA()


    > histograms
    
      histograms are in most cases not more than a means to an
      end. they are required for color reduction, statistics, and
      for certain low-level functions. render.library supports
      'real' truecolor histograms with up to 24bit resolution.
      this feature is rarely found (and rarely needed, but
      sometimes it cannot be dispensed).

      - CreateHistogramA()
      - AddChunkyImageA()
      - AddRGBImageA()
      - AddRGB()
      - AddHistogram()
      - CountRGB()
      - QueryHistogram()


    > color reduction (quantization)
    
      this task is everything but trivial. render.library
      uses a sophisticated algorythm that can stand the test
      with the most-elaborated implementations on the Amiga
      platform.

      - ExtractPaletteA()


    > truecolor to chunky conversion (rendering)

      this task was the initial idea for render.library, and
      render.library deserves its name. just have a look at all
      those possible taglist arguments for
      
      - RenderA()


    > chunky to chunky conversion
        
      this is similar to RenderA(), only that it accepts
      chunky bytes as input.
        
      - ConvertChunkyA()
      - CreatePenTableA()


    > mapping
    
      in addition to those fully-featured rendering and
      chunky-conversion functions, render.library offers a
      low-level conversion class called mapping-engine.
      
      - CreateMapEngineA()
      - MapChunkyArrayA()
      - MapRGBArrayA()
    

    > scaling
    
      render.library provides an own scaling class. its instances
      are called scaling-engines. the performance of
      non-interpolating scaling-engines comes close to CopyMem().
      Scaling-engines may be passed to RenderA() and
      ConvertChunkyA(), allowing scaling and rendering in a
      single pass.

      - CreateScaleEngineA()
      - ScaleA()
      - ScaleOrdinate()


    > alpha-channel

      this is a trivial task with render.library and can be
      achieved with these functions:
        
      - InsertAlphaChannelA()
      - ExtractAlphaChannelA()
      - ApplyAlphaChannelA()
      - MixRGBArrayA()


    > memory management

      render.library features a lean and simple, but yet
      effective memory management system throughout these
      functions:
        
      - CreateRMHandlerA()
      - AllocRenderMem()
      - AllocRenderVec()



data types
-----------------------------

  what kind of data can be processed with render.library?
  currently, there are the following formats available:

    > PIXFMT_0RGB_32          (ULONG)

      longword truecolor pixels, with the sequence
        
      0x00rrggbb
        
      the upmost byte is not defined, and in most cases may
      contain alpha-channel information.
        
    > PIXFMT_CHUNKY_CLUT      (UBYTE)
        
      chunky-byte pixels with color-lookup table. a pixel's
      actual color is not defined throughout this format.
      therefore you need a palette for most operations with
      arrays of chunky bytes. usually, the byte value
      acts directly as an index to the color-lookup table.


  for certain operations, an additional specification is required
  for the access to a color-lookup table:

    > COLORMODE_CLUT
    
      this mode defines 'normal' color lookup (see above). one
      byte directly acts as an index to a color-lookup table.

    > COLORMODE_HAM6

      this mode defines HAM6 color lookup. one byte does not
      necessarily reference a palette index.

    > COLORMODE_HAM6

      this mode defines HAM8 color lookup. one byte does not
      necessarily reference a palette index.

  remember that COLORMODE_HAM6 and COLORMODE_HAM8 still apply to
  chunky pixels, although HAM is an Amiga specific format that was
  (unfortunately) never defined for chunky bytes, but rather for
  planar data ('bitplanes').



memory management
-----------------------------

  since image processing is in general quite hungry for memory
  resources, render.library offers a both simple and effective
  custom memory management.
  
  you don't need to use it, but it may help you to make things
  easier and faster. under normal circumstances, you probably want
  to set up a pooled memory environment. pooled memory helps to
  avoid memory fragmentation, and it preserves the system's public
  memory lists from too much stressing. create a pooled memhandler
  as follows:

    memhandler = CreateRMHandler(RND_MemType, RMHTYPE_POOLED,
	 TAG_DONE);

  * note: pooled memory is not available prior to exec v39. if
  not available, just create a 'standard' memory handler with
  RMHTYPE_PUBLIC - the default value. if your project won't
  include many large images, palettes and histograms, you
  might as well create a 'standard' memory handler.

  you might even live without a memhandler, since the memhandler
  argument is always optional - public memory will be used in
  this case. nevertheless, i recommend to use a memhandler. it
  helps you to easily upgrade your code to a more sophisticated
  memory management when necessary. for the use with dynamic
  histograms (see next section), a pooled memory handler is
  highly advised.

  you may also set up a memory manager that operates in a static
  block of memory. this is similar to the memory management of
  programs like AdPro. they grab a large block of memory at
  startup and allocate all intermediate buffers from this memory
  area. example:

    memhandler = CreateRMHandler(RND_MemType, RMHTYPE_PRIVATE,
        RND_MemBlock, my_memblock,
        RND_MemSize, my_memblock_size, TAG_DONE);

  * note: private memory management is less flexible than a
  pooled memory environment. once the static buffer is full or
  becomes fragmented, further allocations will fail. v39 exec
  pools will automatically grow and shrink to the required size.
  
  not only render.library functions may profit from a memhandler.
  your application has access to it throughout these functions:

    mem = AllocRenderMem(memhandler, size);
    FreeRenderMem(memhandler, mem, size);

    mem = AllocRenderVec(memhandler, size);
    FreeRenderVec(mem);

  the main idea is to allow your application and render.library to
  share a particular memory pool. this will keep the whole memory
  management lean and effective.



histograms
-----------------------------

  a histogram is an object that holds color information. it
  maintains a counter for each color.

  many functions are related to histograms. their creation is
  quite simple:

    histogram = CreateHistogram(RND_RMHandler, memhandler,
                    RND_HSType, HSTYPE_15BIT_TURBO,
                    TAG_DONE);

  render.library offers two different histogram schemes: the
  _TURBO types are provided with tables, the 'normal' types use
  digital trees. tabular histograms are very fast, but their
  memory consumption is determined according to 2^bits x 4
  bytes. with other words, a 12bit histogram is very small (8192
  bytes), but a 24bit histogram would require 64mb of memory. it
  would be nonsense to create a 24bit tabular histogram anyway,
  since most images do not contain all possible RGB values. that's
  why _TURBO histograms are limited to 12, 15, and 18 bit
  resolution.

  tree histograms are more flexible. their memory consumption is
  not predictable, because they are created dynamically. a
  drawback is that every single RGB entry in a tree histogram
  requires 20 bytes of memory. the tree type allows you to create
  'real' histograms with full 24bit accuracy. by the way, this is
  the only way to evaluate the effective number of different
  colors in an image. (most image processing applications operate
  with 15bit histograms and tell you nonsense about the number of
  colors found.)

  * a 15 bit _TURBO histogram is the best choice in most cases.
  as mentioned before, most image processing applications use
  this resolution. render.library histograms are interpolated.
  this reasonably compensates the loss of information.
  
  now that we've created a histogram, what are we going to do with
  it? first of all, we have to load it with color information.

    success = AddRGBImage(histogram, rgb_array, 
                                640, 480, NULL);

  in this example, a 640480 truecolor image is added to the
  histogram. you may of course add as many pictures as you like,
  e.g. in order to record all the single frames of an animation.
  it's also possible to directly add chunky images to a histogram:

     success = AddChunkyImage(histogram, chunky_array, 640, 480,
            palette, NULL);

  (as you can see, chunky images require a palette. palettes will
  be covered by the next chapter.)

  * note: the return value <success> is not boolean, it's
  defined with constants. you must check for ADDH_SUCCESS, since
  adding data to histograms may fail at any time. your histogram
  won't be corrupted in that case, but if you don't check the
  return value, you can't depend on finding your histogram in a
  state that correctly represents the data you've added.
  
  you can find out about certain histogram parameters with the
  QueryHistogram() function. for instance

    number_of_pixels = QueryHistogram(histogram, RND_NumPixels);
    number_of_colors = QueryHistogram(histogram, RND_NumColors);

  another way to access histograms is the CountRGB() function. it
  returns the number of pixels recorded for a specific RGB value:

    pixels_represented = CountRGB(histogram, 0x00rrggbb);

  * note: according to current definitions, histograms can hold
  the information for 2^32 (4,28 billions) pixels. as long as no
  overflow occurs to a single entry inside the histogram, they
  may even contain 2^32 different colors. further
  histogram-related processing (e.g. quantization) is defined
  for 2^32 pixels, though.



palettes
-----------------------------

  most functions dealing with chunky pixels require color-lookup
  tables. in the context of render.library, they are called
  'palettes'. palettes cover many technical details internally.
  a palette is created this way:

    palette = CreatePalette(NULL);

  this sets up a palette of 15bit resolution. these 15bit suffice
  in most situations, and they are taken into account only if some
  kind of rendering or mapping takes place. more control can be
  obtained via taglist arguments:

    palette = CreatePalette(RND_HSType, HSTYPE_12BIT, TAG_DONE);

  as you can see, a palette's resolution is specified analogously
  to a histogram's resolution.
  
  * note: palettes do not differenciate between _TURBO and
  non-TURBO types.
  
  in addition to that, you can supply CreatePalette() with a
  memhandler (which is not a bad idea at all):

    palette = CreatePalette(RND_RMHandler, memhandler, TAG_DONE);

  after all, what you get is just an empty palette ready for being
  loaded with colors. by default, ImportPaletteA() handles
  0x00rrggbb color entries. you may as well import LoadRGB32-alike
  and LoadRGB4-type tables.

    ULONG colortable[5] =
        {0xff0088, 0x007532, 0x435278, 0xffffff, 0x000000};

    ImportPalette(palette, colortable, 5, NULL);

  you may import colors mulitple times and specify
  offsets:

    ImportPalette(palette, colortable1, 10, NULL);
    ImportPalette(palette, colortable2, 20, RND_NewPalette, FALSE,
        RND_FirstColor, 10, TAG_DONE);

  with RND_NewPalette set to FALSE, ImportPaletteA() merges
  color entries to a palette. the RND_FirstColor tag specifies
  the offset where to add new colors. the total number of
  colors in a palette is automatically maintained and updated.
  the only restriction is that you are neither allowed to
  import more than 256 colors, nor to import beyond the 256th
  entry. the counterpart of ImportPaletteA() is
  ExportPaletteA().

  another way to load a palette with colors is via
  ExtractPaletteA() - refer to the next section.



quantization
-----------------------------

  i've got you here, right? quantization ('color reduction') is
  extremely easy with render.library. you just have to supply a
  source histogram and a destination palette.

    success = ExtractPalette(histogram, palette, numcolors, NULL);

  this is the basic function call. it will extract the given
  number of colors from the histogram and insert it into the
  palette. refer to the autodocs for further details.



rendering
-----------------------------

  now for the climax of it all and to what gave render.library its
  name. let's transform a RGB array to chunky pixels:

    success = Render(rgbarray, width, height, chunkyarray,
                palette, tags);

  there are many tags available, for dithering, offsets, secondary
  conversions, HAM mode support, scaling, callback hooks, and
  more. the same applies to the chunky-byte equivalent:

    success = ConvertChunky(sourcearray, sourcepalette,
        width, height, destarray, destpalette, tags);

  no need to mention that these functions are quite fast. if
  they're still not fast enough for your purposes, refer to the
  next section.



mapping
-----------------------------

  you are disappointed by the speed of Render(), because it is
  only several times faster than any other software on the Amiga?
  then you have to enter the wonderful realm of mapping-engines.
  mapping-engines focus on three aspects: a) speed, b) speed, c)
  speed.
  
  * mapping-engines depend on a destination palette. The
  contents of that destination palette may change, but it must
  not be deleted prior to dependent mapping-engines.
  
  the mapping-engine may additionally depend on a histogram
  containing the color information of those pixels that are
  going to be mapped. this histogram has to be of a _TURBO
  type of the same resolution as the palette. if there's a
  histogram available that fits these specifications, you
  are strongly recommended to use it. this improves speed
  remarkably.

    mapengine = CreateMapEngine(destpalette, NULL);

    mapengine =
        CreateMapEngine(destpalette, RND_Histogram, histogram,
        TAG_DONE);

  now there are two ways of using a mapping-engine. the
  preferrable way is to pass it to RenderA() or ConvertChunkyA()
  with the RND_MapEngine tag argument. if you can manage without
  dithering, scaling, callback hooks and alike, you might as well
  use it with these functions:

    MapRGBArray(mapengine, rgbarray, width, height, chunkyarray,
    tags);

    MapChunkyArray(mapengine, sourcearray, sourcepalette, width,
    height, destarray, tags);

  the above functions have got very low overhead and provide
  rather primitive data transfer schemes. MapRGBArrayA() comes
  close to physical bus performance (with totally 4 memory
  accesses per pixel).
  
  * a drawback with MapRGBArrayA() is that your RGB pixels may not
  contain a trailing alphachannel-byte. it must by set to zero,
  otherwise you must use RenderA() instead.
  
  a call to a function with a mapping-engine can be quite slow
  when invoked for the first time (or whenever the destination
  palette changed). the mapping-engine's internal buffers have
  to be (re-)constructed then. that's where the optional
  histogram gets into action: only those RGB values actually
  appearing in the histogram are updated. if no histogram was
  specified with CreateMapEngineA(), a mapping-table will be
  calculated for the whole RGB space. the palette's resolution
  is a significant factor then: a 12bit RGB space holds 4096
  colors, a 15bit RGB space has got 32768 colors, and a 18bit
  RGB space contains 262144 colors. you better know what you're
  doing when creating a mapping-engine for a 18bit palette and
  do not specify a histogram.

  * note: nothing serious happens if the supplied histogram
  does not correctly represent the data you convert with a
  mapping-engine. however, colors that are not part of the
  histogram would be mapped to nonsense pixels. unfortunately
  this applies to conversions with dithering. you should
  disable dithering when histogram-related mapping-engines are
  passed to RenderA() or ConvertChunkyA().
