| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407 |
- {
- Vampyre Imaging Library Demo
- Vampyre Image Converter (ObjectPascal, low level, Win32/Linux/DOS)
- tested in Delphi 7/10, Free Pascal 2.0.4 (Win32/Linux/DOS)
- written by Marek Mauder
- Image Converter is command line tool for converting images between
- file and data formats. It also provides some basic manipulation functions
- like resizing, rotating, or color reduction.
- See PrintUsage procedure for usage details (or just run binary without parameters).
- Note: Operations (change format, resize, rotate) are processed in the same order
- as they appear on the command line.
- }
- program VampConvert;
- {$APPTYPE CONSOLE}
- {$I ImagingOptions.inc}
- uses
- SysUtils,
- Classes,
- ImagingTypes,
- Imaging,
- ImagingUtility;
- const
- DefaultOutputFile = 'output.png';
- DefaultFileFormat = 'png';
- var
- InFile, OutFile: string;
- Operations: TStringList;
- procedure PrintHeader;
- begin
- WriteLn('Vampyre Image Converter (library version ', Imaging.GetVersionStr, ')');
- WriteLn('by Marek Mauder');
- WriteLn;
- end;
- procedure PrintUsage;
- type
- TFormatInfo = record
- Ext: string;
- CanSave: Boolean;
- end;
- var
- I: LongInt;
- FmtIter: TImageFormat;
- Info: TImageFormatInfo;
- Name, Ext, Masks: string;
- CanSave, IsMulti: Boolean;
- FileFormats: array of TFormatInfo;
- begin
- WriteLn('Usage:');
- WriteLn('VampConvert [-op=arg] [..] -infile=file.ext [..] [-outfile=file.ext] [-op=arg]');
- WriteLn(' Options:');
- WriteLn(' -infile | -i: specify input image file path');
- WriteLn(' -outfile | -o: specify output image file path');
- WriteLn(' argument: file path or "*.ext" where input file name will be used ');
- WriteLn(' but with "ext" extension');
- WriteLn(' Operations:');
- WriteLn(' Note: they are processed in the same order as they appear on command line');
- WriteLn(' -format: changes data format of input images');
- WriteLn(' argument: name of data format supported by Imaging like A8R8G8B8');
- WriteLn(' -resize: changes size of input images');
- WriteLn(' argument: string in format AxBxC (%dx%dx%s) where A is desired');
- WriteLn(' width, B is desired height, and C is resampling filter used.');
- WriteLn(' If A or B is 0 then original dimension will be preserved.');
- WriteLn(' C is optional and can have one of following values: ');
- WriteLn(' nearest(default), bilinear, bicubic.');
- WriteLn(' -flip: flips input images upside down');
- WriteLn(' -mirror: mirrors input images left to right');
- WriteLn(' -colorcount: reduces number of colors in image');
- WriteLn(' argument: number of desired colors (2-4096)');
- WriteLn(' -genmipmaps: generates mipmaps for main image');
- WriteLn(' argument: number of desired mip levels. 0 or no arg means');
- WriteLn(' create all possible levels');
- WriteLn(' -rotate: rotates input images counterclockwise');
- WriteLn(' argument: angle in degrees, multiple of 90');
- // Enumerate all supported file formats and store default ext and
- // their capability to save files to string list.
- I := 0;
- while EnumFileFormats(I, Name, Ext, Masks, CanSave, IsMulti) do
- begin
- SetLength(FileFormats, I);
- FileFormats[I - 1].Ext := Ext;
- FileFormats[I - 1].CanSave := CanSave;
- end;
- // Print all file formats that support loading files (just write all)
- WriteLn;
- WriteLn(' Supported file formats (INPUT):');
- for I := 0 to High(FileFormats) do
- Write(FileFormats[I].Ext, ' ');
- WriteLn;
- // Print all file formats that support saving files
- WriteLn(' Supported file formats (OUTPUT):');
- for I := 0 to High(FileFormats) do
- begin
- if FileFormats[I].CanSave then
- Write(FileFormats[I].Ext, ' ');
- end;
- WriteLn;
- // Iterate over all image data formats and write their names
- Write(' Supported data formats: ');
- for FmtIter := ifIndex8 to High(TImageFormat) do
- begin
- if Imaging.GetImageFormatInfo(FmtIter, Info) then
- Write(Info.Name, ' ');
- end;
- end;
- procedure PrintError(const Msg: string; const Args: array of const);
- begin
- WriteLn(Format('Error: ' + Msg, Args));
- WriteLn;
- PrintUsage;
- Operations.Free;
- Halt(1);
- end;
- procedure PrintWarning(const Msg: string; const Args: array of const);
- begin
- WriteLn(Format('Warning: ' + Msg, Args));
- end;
- procedure PrintInfo(const Msg: string; const Args: array of const);
- begin
- WriteLn(Format('Info: ' + Msg, Args));
- end;
- procedure ParseCommandLine;
- var
- I: LongInt;
- procedure ParseOption(const Opt: string);
- var
- I: LongInt;
- S, Arg: string;
- begin
- S := Opt;
- I := Pos('=', S);
- if I > 0 then
- Arg := Copy(S, I + 1, MaxInt)
- else
- Arg := 'none';
- Delete(S, I, MaxInt);
- Delete(S, 1, 1);
- S := LowerCase(S);
- if (S = 'infile') or (S = 'i') then
- InFile := Arg
- else if (S = 'outfile') or (S = 'o') then
- OutFile := Arg
- else
- Operations.Add(Format('%s=%s', [S, LowerCase(Arg)]));
- end;
- begin
- for I := 1 to ParamCount do
- ParseOption(ParamStr(I));
- end;
- procedure CheckOptions;
- var
- InFileName, InFileDir: string;
- begin
- // Check if input and input filenames are valid
- if InFile = '' then
- PrintError('Input file not specified', []);
- if not FileExists(InFile) then
- PrintError('Input file not found: "%s"', [InFile]);
- if not Imaging.IsFileFormatSupported(InFile) then
- PrintError('Input file format not supported: %s', [ImagingUtility.GetFileExt(InFile)]);
- if OutFile = '' then
- begin
- PrintWarning('Output file not specified, using default: %s (in current directory)',
- [DefaultOutputFile]);
- OutFile := DefaultOutputFile;
- end;
- InFileName := ExtractFileName(InFile);
- InFileDir := ExtractFileDir(InFile);
- InFileDir := Iff(InFileDir <> '', PathDelim, InFileDir);
- // If outpout filename is in format "*.ext" then input filename is used
- // but with "ext" extension
- if ChangeFileExt(ExtractFileName(OutFile), '') = '*' then
- OutFile := InFileDir + ChangeFileExt(InFileName, ExtractFileExt(OutFile));
- if not Imaging.IsFileFormatSupported(OutFile) then
- begin
- PrintWarning('Output file format not supported, using default: %s',
- [DefaultFileFormat]);
- OutFile := InFileDir + ChangeFileExt(InFileName, '.' + DefaultFileFormat);
- end;
- end;
- procedure ProcessOperations;
- var
- I, J, X, Y, NewWidth, NewHeight: LongInt;
- OpName, Arg, S: string;
- Images: TDynImageDataArray;
- Format: TImageFormat;
- ResFilter: TResizeFilter;
- MainImage: TImageData;
- procedure PrintInvalidArg(const OpName, Arg: string);
- begin
- PrintError('Invalid argument (%s) for operation: %s', [Arg, OpName]);
- end;
- function FindFormat(const FmtString: string): TImageFormat;
- var
- I: TImageFormat;
- Name: string;
- begin
- Result := ifUnknown;
- for I := ifIndex8 to High(TImageFormat) do
- begin
- Name := Imaging.GetFormatName(I);
- if SameText(FmtString, Name) or SameText(FmtString, 'if' + Name) then
- begin
- Result := I;
- Exit;
- end;
- end;
- end;
- begin
- Operations.NameValueSeparator := '=';
- InitImage(MainImage);
- try
- // Load input image
- if not Imaging.LoadMultiImageFromFile(InFile, Images) then
- PrintError('Input file loading failed: %s', [ImagingUtility.GetExceptObject.Message]);
- // Check if all loaded images are OK or if they are any at all
- if (Length(Images) = 0) or not Imaging.TestImagesInArray(Images) then
- PrintError('Input file loaded but it does not contain any images or some of them are invalid', []);
- PrintInfo('Input images (count: %d) loaded succesfully from: %s', [Length(Images), InFile]);
- // Now process operations one by one
- for I := 0 to Operations.Count - 1 do
- begin
- // Get operation name and argument
- OpName := Operations.Names[I];
- Arg := Operations.ValueFromIndex[I];
- if OpName = 'format' then
- begin
- // Check if argument is name of some data format
- Format := FindFormat(Arg);
- if Format = ifUnknown then
- PrintInvalidArg(OpName, Arg);
- // If some format was found then all images are converted to it
- PrintInfo('Converting images to data format: %s', [Imaging.GetFormatName(Format)]);
- for J := 0 to High(Images) do
- Imaging.ConvertImage(Images[J], Format);
- end
- else if OpName = 'resize' then
- begin
- // Parse argument in format %dx%d[x%s]
- J := Pos('x', Arg);
- if J = 0 then
- PrintInvalidArg(OpName, Arg);
- X := StrToIntDef(Copy(Arg, 1, J - 1), Images[0].Width);
- Delete(Arg, 1, J);
- J := Pos('x', Arg);
- S := 'nearest';
- if J <> 0 then
- begin
- S := Copy(Arg, J + 1, MaxInt);
- Delete(Arg, J, MaxInt);
- end;
- Y := StrToIntDef(Arg, 0);
- // Limit new dimensions to 8192 and convert
- // invalid dimensions are set to 0 which is special value (later)
- X := ClampInt(X, 0, 8192);
- Y := ClampInt(Y, 0, 8192);
- // Select filtering method used for resizing according to argument
- ResFilter := rfNearest;
- if Pos('bil', S) = 1 then
- ResFilter := rfBilinear
- else if Pos('bic', S) = 1 then
- ResFilter := rfBicubic;
- PrintInfo('Resizing images to %dx%d using [%s] filter: ', [X, Y, S]);
- for J := 0 to High(Images) do
- begin
- // If any of new dimensions is 0 we use the original dimension
- // of image
- NewWidth := Iff(X = 0, Images[J].Width, X);
- NewHeight := Iff(Y = 0, Images[J].Height, Y);
- Imaging.ResizeImage(Images[J], NewWidth, NewHeight, ResFilter);
- end;
- end
- else if OpName = 'flip' then
- begin
- // Simply flip all images
- PrintInfo('Flipping images upside down', []);
- for J := 0 to High(Images) do
- Imaging.FlipImage(Images[J]);
- end
- else if OpName = 'mirror' then
- begin
- // Simply mirror all images
- PrintInfo('Mirroring images left to right', []);
- for J := 0 to High(Images) do
- Imaging.MirrorImage(Images[J]);
- end
- else if OpName = 'colorcount' then
- begin
- // Get value of the argument ...
- if not TryStrToInt(Arg, X) then
- PrintInvalidArg(OpName, Arg);
- X := ClampInt(X, 2, 4096);
- PrintInfo('Reducing color count of images to: %d', [X]);
- // ... and reduce number of colors of all images
- for J := 0 to High(Images) do
- Imaging.ReduceColors(Images[J], X);
- end
- else if OpName = 'genmipmaps' then
- begin
- // Get number of mipmaps from argument or use
- // default 0 which means "create all mip levels you can"
- X := StrToIntDef(Arg, 0);
- PrintInfo('Generating mipmaps for main image', []);
- // Clone main image and use input array as the output of
- // mipmap generation function
- Imaging.CloneImage(Images[0], MainImage);
- Imaging.GenerateMipMaps(MainImage, X, Images);
- end
- else if OpName = 'rotate' then
- begin
- // Parse argument, only multiples of 90 degrees are allowed
- if not TryStrToInt(Arg, X) then
- PrintInvalidArg(OpName, Arg);
- if X mod 90 <> 0 then
- PrintInvalidArg(OpName, Arg);
- PrintInfo('Rotating images: %d degrees CCW', [X]);
- // Rotate all
- for J := 0 to High(Images) do
- Imaging.RotateImage(Images[J], X);
- end
- else
- begin
- // Warn about unknown operations passed to program
- PrintWarning('Unrecognized operation: ' + OpName, []);
- end;
- end;
- // Finally save the result
- if not Imaging.SaveMultiImageToFile(OutFile, Images) then
- PrintError('Output file saving failed: %s', [ImagingUtility.GetExceptObject.Message])
- else
- PrintInfo('Output images saved succesfully to: %s', [OutFile])
- finally
- // Free images in array as well as temp image
- Imaging.FreeImagesInArray(Images);
- Imaging.FreeImage(MainImage);
- end;
- end;
- begin
- PrintHeader;
- Operations := TStringList.Create;
- ParseCommandLine;
- CheckOptions;
- try
- ProcessOperations;
- except
- PrintError('Exception raised during processing oprations: %s',
- [ImagingUtility.GetExceptObject.Message]);
- end;
- Operations.Free;
- {
- File Notes:
- -- TODOS ----------------------------------------------------
- - more operations
- - allow changing ImagingOptions too
- -- 0.21 Changes/Bug Fixes -----------------------------------
- - added -i and -o shortcut cmd line parameters and fixed
- FPC 32/64 bit compatibility issue
- - List of supported file formats printed by PrintUsage is now
- dynamic and shows input and output formats separately
- -- 0.19 Changes/Bug Fixes -----------------------------------
- - demo created
- }
- end.
|