2
0

DemoUnit.pas 13 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426
  1. {
  2. Vampyre Imaging Library Demo
  3. Vampyre Image Converter (core low level API)
  4. Image Converter is command line tool for converting images between
  5. file and data formats. It also provides some basic manipulation functions
  6. like resizing, rotating, or color reduction.
  7. See PrintUsage procedure for usage details (or just run binary without parameters).
  8. Note: Operations (change format, resize, rotate) are processed in the same order
  9. as they appear on the command line.
  10. }
  11. unit DemoUnit;
  12. {$I ImagingOptions.inc}
  13. interface
  14. procedure RunDemo;
  15. implementation
  16. uses
  17. SysUtils,
  18. Classes,
  19. ImagingTypes,
  20. Imaging,
  21. ImagingUtility;
  22. const
  23. DefaultOutputFile = 'output.png';
  24. DefaultFileFormat = 'png';
  25. var
  26. InFile, OutFile: string;
  27. Operations: TStringList;
  28. procedure PrintHeader;
  29. begin
  30. WriteLn('Vampyre Image Converter (library version ', Imaging.GetVersionStr, ')');
  31. WriteLn('by Marek Mauder');
  32. WriteLn;
  33. end;
  34. procedure PrintUsage;
  35. type
  36. TFormatInfo = record
  37. Ext: string;
  38. CanSave: Boolean;
  39. end;
  40. var
  41. I: LongInt;
  42. FmtIter: TImageFormat;
  43. Info: TImageFormatInfo;
  44. Name, Ext, Masks: string;
  45. CanSave, IsMulti: Boolean;
  46. FileFormats: array of TFormatInfo;
  47. begin
  48. WriteLn('Usage:');
  49. WriteLn('VampConvert [-op=arg] [..] -infile=file.ext [..] [-outfile=file.ext] [-op=arg]');
  50. WriteLn(' Options:');
  51. WriteLn(' -infile | -i: specify input image file path');
  52. WriteLn(' -outfile | -o: specify output image file path');
  53. WriteLn(' argument: file path or "*.ext" where input file name will be used ');
  54. WriteLn(' but with "ext" extension');
  55. WriteLn(' Operations:');
  56. WriteLn(' Note: they are processed in the same order as they appear on command line');
  57. WriteLn(' -format: changes data format of input images');
  58. WriteLn(' argument: name of data format supported by Imaging like A8R8G8B8');
  59. WriteLn(' -resize: changes size of input images');
  60. WriteLn(' argument: string in format AxBxC where A is desired width,');
  61. WriteLn(' B is desired height, and C is resampling filter used.');
  62. WriteLn(' If A or B is 0 then original dimension will be preserved.');
  63. WriteLn(' C is optional and can have one of following values: ');
  64. WriteLn(' nearest(default), bilinear, bicubic, lanczos.');
  65. WriteLn(' -flip: flips input images upside down');
  66. WriteLn(' -mirror: mirrors input images left to right');
  67. WriteLn(' -colorcount: reduces number of colors in image');
  68. WriteLn(' argument: number of desired colors (2-4096)');
  69. WriteLn(' -genmipmaps: generates mipmaps for main image');
  70. WriteLn(' argument: number of desired mip levels. 0 or no arg means');
  71. WriteLn(' create all possible levels');
  72. WriteLn(' -rotate: rotates input images counterclockwise');
  73. WriteLn(' argument: angle in degrees (integer)');
  74. // Enumerate all supported file formats and store default ext and
  75. // their capability to save files to string list.
  76. I := 0;
  77. while EnumFileFormats(I, Name, Ext, Masks, CanSave, IsMulti) do
  78. begin
  79. SetLength(FileFormats, I);
  80. FileFormats[I - 1].Ext := Ext;
  81. FileFormats[I - 1].CanSave := CanSave;
  82. end;
  83. // Print all file formats that support loading files (just write all)
  84. WriteLn;
  85. WriteLn(' Supported file formats (INPUT):');
  86. for I := 0 to High(FileFormats) do
  87. Write(FileFormats[I].Ext, ' ');
  88. WriteLn;
  89. // Print all file formats that support saving files
  90. WriteLn(' Supported file formats (OUTPUT):');
  91. for I := 0 to High(FileFormats) do
  92. begin
  93. if FileFormats[I].CanSave then
  94. Write(FileFormats[I].Ext, ' ');
  95. end;
  96. WriteLn;
  97. // Iterate over all image data formats and write their names
  98. Write(' Supported data formats: ');
  99. for FmtIter := ifIndex8 to High(TImageFormat) do
  100. begin
  101. if Imaging.GetImageFormatInfo(FmtIter, Info) then
  102. Write(Info.Name, ' ');
  103. end;
  104. end;
  105. procedure PrintErrorAndExit(const Msg: string; const Args: array of const);
  106. begin
  107. WriteLn(Format('Error: ' + Msg, Args));
  108. WriteLn;
  109. PrintUsage;
  110. Operations.Free;
  111. Halt(1);
  112. end;
  113. procedure PrintHelpAndExit;
  114. begin
  115. WriteLn;
  116. PrintUsage;
  117. Operations.Free;
  118. Halt(0);
  119. end;
  120. procedure PrintWarning(const Msg: string; const Args: array of const);
  121. begin
  122. WriteLn(Format('Warning: ' + Msg, Args));
  123. end;
  124. procedure PrintInfo(const Msg: string; const Args: array of const);
  125. begin
  126. WriteLn(Format('Info: ' + Msg, Args));
  127. end;
  128. procedure ParseCommandLine;
  129. var
  130. I: LongInt;
  131. procedure ParseOption(const Opt: string);
  132. var
  133. I: LongInt;
  134. S, Arg: string;
  135. begin
  136. S := Opt;
  137. I := Pos('=', S);
  138. if I > 0 then
  139. Arg := Copy(S, I + 1, MaxInt)
  140. else
  141. Arg := 'none';
  142. Delete(S, I, MaxInt);
  143. Delete(S, 1, 1);
  144. S := LowerCase(S);
  145. if (S = 'infile') or (S = 'i') then
  146. InFile := Arg
  147. else if (S = 'outfile') or (S = 'o') then
  148. OutFile := Arg
  149. else if S = 'h' then
  150. PrintHelpAndExit
  151. else
  152. Operations.Add(Format('%s=%s', [S, LowerCase(Arg)]));
  153. end;
  154. begin
  155. for I := 1 to ParamCount do
  156. ParseOption(ParamStr(I));
  157. end;
  158. procedure CheckOptions;
  159. var
  160. InFileName, InFileDir: string;
  161. begin
  162. // Check if input and output filenames are valid
  163. if InFile = '' then
  164. PrintErrorAndExit('Input file not specified', []);
  165. if not FileExists(InFile) then
  166. PrintErrorAndExit('Input file not found: "%s"', [InFile]);
  167. if not Imaging.IsFileFormatSupported(InFile) then
  168. PrintErrorAndExit('Input file format not supported: %s', [ImagingUtility.GetFileExt(InFile)]);
  169. if OutFile = '' then
  170. begin
  171. PrintWarning('Output file not specified, using default: %s (in current directory)',
  172. [DefaultOutputFile]);
  173. OutFile := DefaultOutputFile;
  174. end;
  175. InFileName := ExtractFileName(InFile);
  176. InFileDir := ExtractFileDir(InFile);
  177. InFileDir := Iff(InFileDir <> '', PathDelim, InFileDir);
  178. // If outpout filename is in format "*.ext" then input filename is used
  179. // but with "ext" extension
  180. if ChangeFileExt(ExtractFileName(OutFile), '') = '*' then
  181. OutFile := InFileDir + ChangeFileExt(InFileName, ExtractFileExt(OutFile));
  182. if not Imaging.IsFileFormatSupported(OutFile) then
  183. begin
  184. PrintWarning('Output file format not supported, using default: %s',
  185. [DefaultFileFormat]);
  186. OutFile := InFileDir + ChangeFileExt(InFileName, '.' + DefaultFileFormat);
  187. end;
  188. end;
  189. procedure ProcessOperations;
  190. var
  191. I, J, X, Y, NewWidth, NewHeight: Integer;
  192. OpName, Arg, S: string;
  193. Images: TDynImageDataArray;
  194. Format: TImageFormat;
  195. ResFilter: TResizeFilter;
  196. MainImage: TImageData;
  197. procedure PrintInvalidArg(const OpName, Arg: string);
  198. begin
  199. PrintErrorAndExit('Invalid argument (%s) for operation: %s', [Arg, OpName]);
  200. end;
  201. function FindFormat(const FmtString: string): TImageFormat;
  202. var
  203. I: TImageFormat;
  204. Name: string;
  205. begin
  206. Result := ifUnknown;
  207. for I := ifIndex8 to High(TImageFormat) do
  208. begin
  209. Name := Imaging.GetFormatName(I);
  210. if SameText(FmtString, Name) or SameText(FmtString, 'if' + Name) then
  211. begin
  212. Result := I;
  213. Exit;
  214. end;
  215. end;
  216. end;
  217. begin
  218. Operations.NameValueSeparator := '=';
  219. InitImage(MainImage);
  220. try
  221. // Load input image
  222. if not Imaging.LoadMultiImageFromFile(InFile, Images) then
  223. PrintErrorAndExit('Input file loading failed: %s', [ImagingUtility.GetExceptObject.Message]);
  224. // Check if all loaded images are OK or if they are any at all
  225. if (Length(Images) = 0) or not Imaging.TestImagesInArray(Images) then
  226. PrintErrorAndExit('Input file loaded but it does not contain any images or some of them are invalid', []);
  227. PrintInfo('Input images (count: %d) loaded succesfully from: %s', [Length(Images), InFile]);
  228. // Now process operations one by one
  229. for I := 0 to Operations.Count - 1 do
  230. begin
  231. // Get operation name and argument
  232. OpName := Operations.Names[I];
  233. Arg := Operations.ValueFromIndex[I];
  234. if OpName = 'format' then
  235. begin
  236. // Check if argument is name of some data format
  237. Format := FindFormat(Arg);
  238. if Format = ifUnknown then
  239. PrintInvalidArg(OpName, Arg);
  240. // If some format was found then all images are converted to it
  241. PrintInfo('Converting images to data format: %s', [Imaging.GetFormatName(Format)]);
  242. for J := 0 to High(Images) do
  243. Imaging.ConvertImage(Images[J], Format);
  244. end
  245. else if OpName = 'resize' then
  246. begin
  247. // Parse argument in format %dx%d[x%s]
  248. J := Pos('x', Arg);
  249. if J = 0 then
  250. PrintInvalidArg(OpName, Arg);
  251. X := StrToIntDef(Copy(Arg, 1, J - 1), Images[0].Width);
  252. Delete(Arg, 1, J);
  253. J := Pos('x', Arg);
  254. S := 'nearest';
  255. if J <> 0 then
  256. begin
  257. S := Copy(Arg, J + 1, MaxInt);
  258. Delete(Arg, J, MaxInt);
  259. end;
  260. Y := StrToIntDef(Arg, 0);
  261. // Limit new dimensions and convert
  262. // invalid dimensions are set to 0 which is special value (later)
  263. X := ClampInt(X, 0, 32768);
  264. Y := ClampInt(Y, 0, 32768);
  265. // Select filtering method used for resizing according to argument
  266. ResFilter := rfNearest;
  267. if Pos('bil', S) = 1 then
  268. ResFilter := rfBilinear
  269. else if Pos('bic', S) = 1 then
  270. ResFilter := rfBicubic
  271. else if Pos('lan', S) = 1 then
  272. ResFilter := rfLanczos;
  273. PrintInfo('Resizing images to %dx%d using [%s] filter: ', [X, Y, S]);
  274. for J := 0 to High(Images) do
  275. begin
  276. // If any of new dimensions is 0 we use the original dimension
  277. // of image
  278. NewWidth := Iff(X = 0, Images[J].Width, X);
  279. NewHeight := Iff(Y = 0, Images[J].Height, Y);
  280. Imaging.ResizeImage(Images[J], NewWidth, NewHeight, ResFilter);
  281. end;
  282. end
  283. else if OpName = 'flip' then
  284. begin
  285. // Simply flip all images
  286. PrintInfo('Flipping images upside down', []);
  287. for J := 0 to High(Images) do
  288. Imaging.FlipImage(Images[J]);
  289. end
  290. else if OpName = 'mirror' then
  291. begin
  292. // Simply mirror all images
  293. PrintInfo('Mirroring images left to right', []);
  294. for J := 0 to High(Images) do
  295. Imaging.MirrorImage(Images[J]);
  296. end
  297. else if OpName = 'colorcount' then
  298. begin
  299. // Get value of the argument ...
  300. if not TryStrToInt(Arg, X) then
  301. PrintInvalidArg(OpName, Arg);
  302. X := ClampInt(X, 2, 4096);
  303. PrintInfo('Reducing color count of images to: %d', [X]);
  304. // ... and reduce number of colors of all images
  305. for J := 0 to High(Images) do
  306. Imaging.ReduceColors(Images[J], X);
  307. end
  308. else if OpName = 'genmipmaps' then
  309. begin
  310. // Get number of mipmaps from argument or use
  311. // default 0 which means "create all mip levels you can"
  312. X := StrToIntDef(Arg, 0);
  313. PrintInfo('Generating mipmaps for main image', []);
  314. // Clone main image and use input array as the output of
  315. // mipmap generation function
  316. Imaging.CloneImage(Images[0], MainImage);
  317. Imaging.GenerateMipMaps(MainImage, X, Images);
  318. end
  319. else if OpName = 'rotate' then
  320. begin
  321. // Parse argument, only integer degrees are allowed
  322. if not TryStrToInt(Arg, X) then
  323. PrintInvalidArg(OpName, Arg);
  324. PrintInfo('Rotating images: %d degrees CCW', [X]);
  325. // Rotate all
  326. for J := 0 to High(Images) do
  327. Imaging.RotateImage(Images[J], X);
  328. end
  329. else
  330. begin
  331. // Warn about unknown operations passed to program
  332. PrintWarning('Unrecognized operation: ' + OpName, []);
  333. end;
  334. end;
  335. // Copy metadata if present
  336. GlobalMetadata.CopyLoadedMetaItemsForSaving;
  337. // Finally save the result
  338. if not Imaging.SaveMultiImageToFile(OutFile, Images) then
  339. PrintErrorAndExit('Output file saving failed: %s', [ImagingUtility.GetExceptObject.Message])
  340. else
  341. PrintInfo('Output images saved succesfully to: %s', [OutFile])
  342. finally
  343. // Free images in array as well as temp image
  344. Imaging.FreeImagesInArray(Images);
  345. Imaging.FreeImage(MainImage);
  346. end;
  347. end;
  348. procedure RunDemo;
  349. begin
  350. PrintHeader;
  351. Operations := TStringList.Create;
  352. ParseCommandLine;
  353. CheckOptions;
  354. try
  355. ProcessOperations;
  356. except
  357. PrintErrorAndExit('Exception raised during processing oprations: %s',
  358. [ImagingUtility.GetExceptObject.Message]);
  359. end;
  360. Operations.Free;
  361. end;
  362. {
  363. File Notes:
  364. -- 0.80 -----------------------------------------------------
  365. - Added Lanczos as a resampling option
  366. - Removed no longer required limit on rotation angles to be multimples of 90.
  367. -- 0.77.1 ---------------------------------------------------
  368. - Refactored the demo (moved stuff to unit from dpr) and
  369. added Lazarus project files.
  370. -- 0.21 Changes/Bug Fixes -----------------------------------
  371. - added -i and -o shortcut cmd line parameters and fixed
  372. FPC 32/64 bit compatibility issue
  373. - List of supported file formats printed by PrintUsage is now
  374. dynamic and shows input and output formats separately
  375. -- 0.19 Changes/Bug Fixes -----------------------------------
  376. - demo created
  377. }
  378. end.