VampConvert.dpr 13 KB

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