/*
 *  OPTICON.C
 */

#define VERSION "1.6"
static char versiontag[] = "$VER: $Id: opticon.c,v 1.8 1994/07/17 22:41:11 tf Exp $";

/****** OptIcon ******************************************
*
*   NAME
*	OptIcon -- Optimize icon images for size and speed (V36)
*
*   SYNOPSIS
*	OptIcon NAME/A/M,DEPTH=PLANES/N,NOEXPAND/S,CRITICAL/S,VERBOSE/S
*
*   FUNCTION
*	OptIcon reads in given ".info" files and scans the icon image
*	in order to optimize the PlanePick and PlaneOnOff fields in the
*	icon Image structure.  This is a space-saving mechanism for image
*	data.
*	Rather than defining the image data for every plane of the RastPort,
*	you need define data only for the planes that are not entirely zero
*	or one.  As you define your Imagery, you will often find that most
*	of the planes ARE just as color selectors.  For instance, if you're
*	designing a two-color icon to use colors one and three, and the icon
*	will reside in a five-plane display, bit plane zero of your
*	imagery would be all ones, bit plane one would have data that
*	describes the imagery, and bit planes two through four would be
*	all zeroes.  Using these flags avoids wasting all that memory in
*	this way:  first, you specify which planes you want your data to
*	appear in using the PlanePick variable.  For each bit set in the
*	variable, the next "plane" of your image data is blitted to the
*	display.  For each bit clear in this variable, the corresponding bit
*	in PlaneOnOff is examined.  If that bit is clear, a "plane" of zeroes
*	will be used.  If the bit is set, ones will go out instead.
*	Note that if you want an Image that is only a filled rectangle, you
*	can get this by setting PlanePick to zero (pick no planes of data)
*	and set PlaneOnOff to describe the pen color of the rectangle.
*
*   INPUTS
*	NAME          - name of the icon image file.  A trailing ".info"
*			is optional but not required.
*	DEPTH         - maximum number of bitplanes to be saved.
*	VERBOSE       - display input and output information for each icon.
*
*   EXAMPLE
*	;Remove all but the first 3 planes of the icon image for the
*	;disk in drive DF0: but don't add any planes
*	opticon df0:disk planes=3 noexpand
*
*   NOTES
*	Since the IconEdit from Commodore will always save 8 bitplane icons
*	the above example might be of great use to you.  (Note that 3 plane
*	images are not only smaller but also faster!)
*	Coming with OptIcon is the script PatchIcons which will recursively
*	descend all subdirectories of a given path deleting all but the first
*	3 planes of all icon images in that path.
*
*	OptIcon now also allows you to expand your 8 or more color icons
*	for the use on a 16 or more color Workbench.  This is important due
*	to the new color system under OS3.x which always shifts the second
*	four colors to the end of the system palette.  Therefore you might
*	want to adapt an icon's color depth to the actual screenmode it is
*	used on.
*
*   EXAMPLE
*	;Remap the last 4 of at least 8 colors of the RAM DISK icon
*	;to the last 4 colors in a 16 or more colors Workbench palette
*	opticon ram:disk planes=4
*
*   BUGS
*	Commodore's PutDiskObject() currently [icon.library 40.1 (15.2.93)]
*	re-expands icon images using the PlanePick/PlaneOnOff mechanism and
*	in fact PutDiskObject() has quite a lot of problems doing so!
*	For this reason OptIcon will perform the PlanePick/PlaneOnOff
*	optimization only if the keyword CRITICAL is given in the command
*	line.
*
*   DISCLAIMER
*	This file is part of the Icon2C and OptIcon distribution.
*
*	Icon2C and OptIcon are free software; you can redistribute them
*	and/or modify them under the terms of the GNU General Public License
*	as published by the Free Software Foundation; either version 1 of
*	the License, or (at your option) any later version.
*
*	Icon2C and Opticon are distributed in the hope that they will be
*	useful, but WITHOUT ANY WARRANTY; without even the implied warranty
*	of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
*	GNU General Public License for more details.
*
*	You should have received a copy of the GNU General Public License
*	along with these programs; see the file COPYING.  If not, write to
*	the Free Software Foundation, 675 Mass Ave, Cambridge, MA 02139, USA.
*
*   COPYRIGHT
*	Icon2C and OptIcon are Copyright (C)1994 by Tobias Ferber.
*	You can reach me via E-mail: ukjg@rz.uni-karlsruhe.de
*
*   SEE ALSO
*	PatchIcons, Icon2C
*
******************************************************************************
*
*	Compile w/ -DDEBUG to output more information at runtime
*/

#include <ctype.h>
#include <stdlib.h>
#include <string.h>
#include <stdio.h>

#include <exec/types.h>
#include <exec/memory.h>

#include <dos/dos.h>
#include <dos/rdargs.h>

#include <intuition/intuition.h>
#include <intuition/intuitionbase.h>

#include <workbench/workbench.h>
#include <workbench/startup.h>
#include <workbench/icon.h>

#include "memfn.h"

#ifdef __GNUC__
/* suggest parentheses around assignment used as truth value */
#define if(assignment) if( (assignment) )
#endif /* __GNUC__ */

extern struct Library *OpenLibrary(STRPTR, ULONG);
extern void CloseLibrary(struct Library *);
extern void CopyMem(APTR, APTR, ULONG);
extern void *AllocMem(ULONG, ULONG);
extern void FreeMem(void *, ULONG);
extern ULONG TypeOfMem(void *);
extern struct RDArgs *ReadArgs(STRPTR, LONG *, struct RDArgs *);
extern LONG IoErr(void);
extern BOOL PrintFault(LONG, STRPTR);
extern void FreeArgs(struct RDArgs *);
extern struct DiskObject *GetDiskObject(char *);
extern BOOL PutDiskObject(char *, struct DiskObject *);
extern void FreeDiskObject(struct DiskObject *);

struct IconBase *IconBase;

void display_version_information(void)
{
  static char license[]=
    "OptIcon is free software; you can redistribute it and/or modify\n"
    "it under the terms of the GNU General Public License as published\n"
    "by the Free Software Foundation; either version 1 of the License,\n"
    "or (at your option) any later version.\n"
    "\n"
    "OptIcon is distributed in the hope that it will be useful,\n"
    "but WITHOUT ANY WARRANTY; without even the implied warranty of\n"
    "MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n"
    "GNU General Public License for more details.\n"
    "\n"
    "You should have received a copy of the GNU General Public License\n"
    "along with OptIcon; see the file COPYING.  If not, write to the\n"
    "Free Software Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.\n"
    ;

  puts("\nOptIcon Version " VERSION " (compiled " __DATE__ ", " __TIME__ ")\n"
       "(c)Copyright 1994 by Tobias Ferber, ukjg@rz.uni-karlsruhe.de\n");

  puts(license);
}


struct Image *free_image(struct Image *i)
{
  if(i)
  {
    long size= i->Depth * i->Height * ((i->Width + 15) / 16) * sizeof(UWORD);

    if(i->ImageData && size > 0)
      FreeMem( i->ImageData, size );

    FreeMem( i, sizeof(struct Image) );
  }

  return (struct Image *)0L;
}

/* command line options */
#define OPT_NOEXPAND  (1<<0)
#define OPT_CRITICAL  (1<<1)
#define OPT_VERBOSE   (1<<2)
#define OPT_REMAPV37  (1<<3)

struct Image *optimize_image(struct Image *i, WORD planes, int optimode)
{
  UWORD p16= i->Height * ((i->Width + 15) / 16); /* #of words per plane */
  UWORD *idata= i->ImageData;
  WORD d, dmax, D, P;
  UBYTE pp= 0;    /* plane pick */
  UBYTE p10= 0;   /* plane on/off */

  struct Image *o= i;  /* optimized image */

  /* prevent silly args from being harmful */

  if(!i)
    return i;

  if(planes > 8)
    planes= 8;

  if(optimode & OPT_VERBOSE)
  {
    printf("(depth=%d, pick=%d, onoff=%d)", i->Depth,
                                            i->PlanePick,
                                            i->PlaneOnOff);
    fflush(stdout);
  }

  /*
      PRESCAN:  Examine dmax planes of i and compute

        D   = the real depth (without trailing 0 planes)
        pp  = the new PlanePick value
        p10 = the new PlaneOnOff value
  */

  dmax= (0 < planes && planes < i->Depth) ? planes : i->Depth;

  for(d= D= 0; d<dmax; d++)
  {
    /* check if we have some image data for plane d */
    if(i->PlanePick & (1<<d))
    {
      UWORD n, *p, v;

      /* scan the image data of plane d */
      for(n=0, p=idata, v=*p; n < p16; n++, p++)
        if(*p != v)
          break;

      if(n==p16 && v==0xFFFF) /* plane d is entirely 1 */
        p10 |= (1<<d);  /* pp bit is already 0 */

      if( n!=p16 || (n==p16 && v!=0x0000 && v!=0xFFFF) )
        pp |= (1<<d);

      if( n!=p16 || (n==p16 && v!=0x0000) )
        D= d;

      idata= &idata[p16];
    }
    else if(i->PlaneOnOff & (1<<d))
    {
      p10 |= (1<<d);
      D=d;
    }
  }

  ++D;

  if( (optimode & OPT_VERBOSE) && (D != dmax || pp != i->PlanePick || p10 != i->PlaneOnOff) )
  {
    printf(" -> (%d,%d,%d)", D,pp,p10);
    fflush(stdout);
  }

  /* compute the #of planes in the output image */
  P= ( planes>D && !(optimode & OPT_NOEXPAND) ) ? planes : D;

  if(P != i->Depth || pp != i->PlanePick || p10 != i->PlaneOnOff)
  {
    UWORD p8= p16 * sizeof(UWORD);
    UWORD *odata;
    ULONG osize= P * p8;

    if( o= (struct Image *)AllocMem(sizeof(struct Image),MEMF_CLEAR) )
    {
      if( odata= (UWORD *)AllocMem(osize,TypeOfMem(i->ImageData)|MEMF_CLEAR) )
      {
        CopyMem( (APTR)i, (APTR)o, sizeof(struct Image) );
        o->ImageData= odata;

        idata= i->ImageData;

        for(d=0; d<D; d++)
        {
          if( pp & (1<<d) )
          {
            if(i->PlanePick & (1<<d))
            {
              CopyMem( (APTR)idata, (APTR)odata, p8 );
              idata= &idata[p16];
              odata= &odata[p16];
            }
            else /* !PlanePick bit (should not happen) */
            {
              memset( (char *)odata, (i->PlaneOnOff & (1<<d)) ? 0xFF : 0x00, p8 );
              odata= &odata[p16];
            }
          }
          else /* !pp bit */
          {
            if(i->PlanePick & (1<<d))
              idata= &idata[p16];

            if( !(optimode & OPT_CRITICAL) )
            {
              memset( (char *)odata, (p10 & (1<<d)) ? 0xFF : 0x00, p8 );
              odata= &odata[p16];
              pp |= (1<<d);
              p10 &= ~(1<<d);
            }
          }
        }

        if(D>=3 && D<P) /* no need to check OPT_NOEXPAND sice P is < D if set */
        {
          UWORD *p;

          if( p= (UWORD *)malloc(p8) )
          {

            if( (optimode & OPT_REMAPV37) && p10<=7 )
            {
              /*
                  REMAP:  Make colors 4-7 become the last 4 in the palette

                  Algo: (1) OR together all planes > 2,
                        (2) invert the result,
                        (3) AND it with plane 2 and
                        (4) OR the result with all planes > 2

                  Note: There is no need to expand the image data if p10 &~ %111 != 0
              */

              /* move to plane 2 */

              idata= i->ImageData;

              for(d=0; d<2; d++)
                if(i->PlanePick & (1<<d))
                  idata= &idata[p16];

              if(i->PlanePick & (1<<2))
              {
                memcpy((char *)p, (char *)idata, p8);
                idata= &idata[p16];
              }
              else memset( (char *)p, (p10 & (1L<<2)) ? 0xFF : 0x00, p8 );

              /* or planes 3..D, invert them, AND the result with plane 2 */

              for(d=3; d<D; d++)
              {
                if(i->PlanePick & (1<<d))
                {
                  memandnot( (char *)p, (char *)idata, p8 );
                  idata= &idata[p16];
                }
                /* else bit d of i->PlaneOnOff is 0 --> no-op */
              }

              /* move to plane 3 */

              odata= o->ImageData;

              for(d=0; d<3; d++)
                if(pp & (1<<d))
                  odata= &odata[p16];

              for(d=3; d<P; d++)
              {
                if( d>=D || pp & (1<<d) )
                {
                  memor( (char *)odata, (char *)p, p8 );
                  odata= &odata[p16];
                  pp |= (1<<d);
                }
              }
            }

            else /* !REMAPV37 */
            {
              /*
                  EXPAND:  Remap the last 4 colors of i to the last 4 colors of o

                  Algo: (1) OR together all planes but the last
                        (2) AND the result with the last plane
                        (3) set the result in all new planes

                  Note: if any plane of i but the last is entirely 1 then we can
                        simply copy the last plane of i to all new planes in o
              */

              idata= i->ImageData;

              if( p10 &~ (1<<(D-1)) == 0)
              {
                memset( (char *)p, 0x00, p8 );

                for(d=0; d<D-1; d++)
                {
                  if(i->PlanePick & (1<<d))
                  {
                    memor( (char *)p, (char *)idata, p8 );
                    idata= &idata[p16];
                  }
                }
                /* else plane d is entirely 0 */

                if( i->PlanePick & (1<<(D-1)) )
                  memand( (char *)p, (char *)idata, p8 );
                /* else the last plane is entirely 1 */
              }
              else /* move to the last plane */
              {
                for(d=0; d<D-1; d++)
                  if(i->PlanePick & (1<<d))
                    idata= &idata[p16];

                if( i->PlanePick & (1<<(D-1)) )
                  memcpy( (char *)p, (char *)idata, p8 );
                else
                  memset( (char *)p, 0xFF, p8 );
              }

              /* move to plane D */

              odata= o->ImageData;

              for(d=0; d<D; d++)
                if(pp & (1<<d))
                  odata= &odata[p16];

              for(d=D; d<P; d++)
              {
                memcpy( (char *)odata, (char *)p, p8 );
                odata= &odata[p16];
                pp |= (1<<d);
              }

            }

            free(p);
          }
          else /* !p --> panic! */
          {
            FreeMem(o->ImageData,osize);
            FreeMem(o,sizeof(struct Image));
            o= (struct Image *)0L;
          }
        }

        o->Depth= P;
        o->PlanePick= pp;
        o->PlaneOnOff= p10;

      }
      else /* !odata */
      {
        FreeMem(o,sizeof(struct Image));
        o= (struct Image *)0L;
      }
    }
  }

  if(optimode & OPT_VERBOSE)
  {
    if(o && o!=i)
      printf(" -> (%d,%d,%d)", o->Depth, o->PlanePick, o->PlaneOnOff);
    putchar('\n');
  }

  return o;
}


int main(int argc, char **argv)
{
  struct RDArgs *a;
  LONG args[6] = { 0,0,0,0,0,0 };

  WORD numplanes= 0;
  char *whoami= *argv;
  int rc= RETURN_OK;

  if( a= ReadArgs("NAME/A/M,DEPTH=PLANES/N,NOEXPAND/S,CRITICAL/S,REMAPV37/S,VERBOSE/S", args, NULL) )
  {
    char **flist= (char **)args[0];

    if(args[1])
    {
      if( (numplanes= (WORD)*(LONG *)(args[1])) < 1 )
      {
        fprintf(stderr,"%s: Illegal maximum depth %d\n",whoami,numplanes);
        rc= RETURN_FAIL;
      }
    }

    if(flist && rc == RETURN_OK)
    {
      if( IconBase= (struct IconBase *)OpenLibrary(ICONNAME,36) )
      {
        while(*flist && rc == RETURN_OK)
        {
          char *iname= (char *)malloc( strlen(*flist) + 1 );

          if(iname)
          {
            struct DiskObject *icon;
            strcpy(iname, *flist);

            if( (icon= GetDiskObject(iname)) == NULL )
            {
              int x= strlen(iname) - 5;
              if(x>0 && !stricmp(&(iname[x]),".info"))
              {
                iname[x]= '\0';
                icon= GetDiskObject(iname);
              }
            }

            if(icon)
            {
              struct Gadget *g= &icon->do_Gadget;

              struct Image *ogr, *osr;
              int modified= 0;
              int flags= 0;

              ogr= osr= (struct Image *)0L;

              if(args[2]) flags |= OPT_NOEXPAND;
              if(args[3]) flags |= OPT_CRITICAL;
              if(args[4]) flags |= OPT_REMAPV37;
              if(args[5]) flags |= OPT_VERBOSE;

              if(g->GadgetRender && (g->Flags & GFLG_GADGIMAGE))
              {
                struct Image *i= (struct Image *)g->GadgetRender;

                if(flags & OPT_VERBOSE)
                  printf("Normal   ");

                if( ogr= optimize_image(i,numplanes,flags) )
                {
                  if(ogr != i)
                  {
                    g->GadgetRender= (APTR)ogr;
                    ++modified;
                  }
                  else ogr= (struct Image *)0L; /* don't free ogr */

                  if(g->SelectRender && (g->Flags & GFLG_GADGHIMAGE))
                  {
                    i= (struct Image *)g->SelectRender;

                    if(flags & OPT_VERBOSE)
                      printf("Selected ");

                    if( osr= optimize_image(i,numplanes, flags) )
                    {
                      if(osr != i)
                      {
                        g->SelectRender= (APTR)osr;
                        ++modified;
                      }
                      else osr= (struct Image *)0L; /* don't free osr */
                    }
                    else
                    {
                      fprintf(stderr,"%s: %s.info: not enough free memory to optimize the selected image\n",whoami,iname);
                      rc= RETURN_ERROR;
                    }
                  }
                }
                else
                {
                  fprintf(stderr,"%s: %s.info: not enough free memory to optimize the normal image\n",whoami,iname);
                  rc= RETURN_ERROR;
                }
              }

              if( modified && rc == RETURN_OK )
              {
                if( !PutDiskObject(iname,icon) )
                  PrintFault(rc= IoErr(), iname);
              }

              if(ogr) ogr= free_image(ogr);
              if(osr) osr= free_image(osr);

              FreeDiskObject(icon);
            }
            else /* !icon */
            {
              fprintf(stderr,"%s: GetDiskObject() failed for %s[.info]\n",whoami,iname);
              rc= IoErr();
            }

            free(iname);
          }
          else /* !iname */
          {
            fprintf(stderr,"%s: out of memory... aaaiiiiiieeeeeeeee!\n",whoami);
            rc= RETURN_ERROR;
          }

          ++flist;
        }
        CloseLibrary((struct Library *)IconBase);
      }
      else
      {
        fprintf(stderr,"%s: You need %s V36+",whoami,ICONNAME);
        rc= RETURN_ERROR;
      }
    }
    FreeArgs(a);
  }
  else /* !ReadArgs */
  {
    if(argc == 1)
      display_version_information();
    else
      PrintFault(IoErr(), NULL);
  }

  exit(rc);
}
