You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
443 lines
14 KiB
443 lines
14 KiB
{
|
|
Deskew
|
|
by Marek Mauder
|
|
http://galfar.vevb.net/deskew
|
|
|
|
The contents of this file are used with permission, subject to the Mozilla
|
|
Public License Version 1.1 (the "License"); you may not use this file except
|
|
in compliance with the License. You may obtain a copy of the License at
|
|
http://www.mozilla.org/MPL/MPL-1.1.html
|
|
|
|
Software distributed under the License is distributed on an "AS IS" basis,
|
|
WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License for
|
|
the specific language governing rights and limitations under the License.
|
|
|
|
Alternatively, the contents of this file may be used under the terms of the
|
|
GNU Lesser General Public License (the "LGPL License"), in which case the
|
|
provisions of the LGPL License are applicable instead of those above.
|
|
If you wish to allow use of your version of this file only under the terms
|
|
of the LGPL License and not to allow others to use your version of this file
|
|
under the MPL, indicate your decision by deleting the provisions above and
|
|
replace them with the notice and other provisions required by the LGPL
|
|
License. If you do not delete the provisions above, a recipient may use
|
|
your version of this file under either the MPL or the LGPL License.
|
|
|
|
For more information about the LGPL: http://www.gnu.org/copyleft/lesser.html
|
|
}
|
|
|
|
unit CmdLineOptions;
|
|
|
|
interface
|
|
|
|
uses
|
|
{$IFNDEF FPC}
|
|
Types,
|
|
StrUtils,
|
|
{$ENDIF}
|
|
SysUtils,
|
|
Classes,
|
|
ImagingTypes,
|
|
ImagingUtility,
|
|
ImageUtils;
|
|
|
|
const
|
|
DefaultThreshold = 128;
|
|
DefaultMaxAngle = 10;
|
|
DefaultSkipAngle = 0.01;
|
|
SDefaultOutputFile = 'out.png';
|
|
|
|
type
|
|
TThresholdingMethod = (
|
|
// Use explicit threshold [0..255]
|
|
tmExplicit,
|
|
// Use adaptive thresholding: Otsu's method
|
|
tmOtsu
|
|
);
|
|
|
|
TOperationalFlag = (
|
|
ofAutoCrop,
|
|
ofDetectOnly
|
|
);
|
|
TOperationalFlags = set of TOperationalFlag;
|
|
|
|
TCmdLineOptions = class
|
|
private
|
|
FInputFile: string;
|
|
FOutputFile: string;
|
|
FMaxAngle: Double;
|
|
FSkipAngle: Double;
|
|
FResamplingFilter: TResamplingFilter;
|
|
FThresholdingMethod: TThresholdingMethod;
|
|
FThresholdLevel: Integer;
|
|
FContentRect: TRect;
|
|
FBackgroundColor: TColor32;
|
|
FForcedOutputFormat: TImageFormat;
|
|
FOperationalFlags: TOperationalFlags;
|
|
FShowStats: Boolean;
|
|
FShowParams: Boolean;
|
|
FShowTimings: Boolean;
|
|
FJpegCompressionQuality: Integer;
|
|
FTiffCompressionScheme: Integer;
|
|
FFormatSettings: TFormatSettings;
|
|
FErrorMessage: string;
|
|
function GetIsValid: Boolean;
|
|
public
|
|
constructor Create;
|
|
// Parses command line arguments to get optiosn set by user
|
|
function ParseCommnadLine: Boolean;
|
|
function OptionsToString: string;
|
|
|
|
property InputFile: string read FInputFile;
|
|
property OutputFile: string read FOutputFile;
|
|
// Max expected rotation angle - algo then works in range [-MaxAngle, MaxAngle]
|
|
property MaxAngle: Double read FMaxAngle;
|
|
// Skew threshold angle - skip deskewing if detected skew angle is in range (-MinAngle, MinAngle)
|
|
property SkipAngle: Double read FSkipAngle;
|
|
// Resampling filter used for rotations
|
|
property ResamplingFilter: TResamplingFilter read FResamplingFilter;
|
|
// Thresholding method used when converting images to binary black/white format
|
|
property ThresholdingMethod: TThresholdingMethod read FThresholdingMethod;
|
|
// Threshold for black/white pixel classification for explicit thresholding method
|
|
property ThresholdLevel: Integer read FThresholdLevel;
|
|
// Rect where to do the skew detection on the page image
|
|
property ContentRect: TRect read FContentRect;
|
|
// Background color for the rotated image
|
|
property BackgroundColor: TColor32 read FBackgroundColor;
|
|
// Forced output format (applied just before saving the output)
|
|
property ForcedOutputFormat: TImageFormat read FForcedOutputFormat;
|
|
// On/Off flags that control parts of the whole operation
|
|
property OperationalFlags: TOperationalFlags read FOperationalFlags;
|
|
// Show skew detection stats
|
|
property ShowStats: Boolean read FShowStats;
|
|
// Show current params to user (for testing etc.)
|
|
property ShowParams: Boolean read FShowParams;
|
|
// Show timing of processing steps to user
|
|
property ShowTimings: Boolean read FShowTimings;
|
|
// Compression quality of JPEG outputs (also embedded) in range [1, 100(best)]
|
|
property JpegCompressionQuality: Integer read FJpegCompressionQuality;
|
|
// Compression scheme of TIFF outputs. Values and default in imaginglib.
|
|
property TiffCompressionScheme: Integer read FTiffCompressionScheme;
|
|
|
|
property IsValid: Boolean read GetIsValid;
|
|
property ErrorMessage: string read FErrorMessage;
|
|
end;
|
|
|
|
implementation
|
|
|
|
uses
|
|
TypInfo, Imaging, ImagingTiff;
|
|
|
|
const
|
|
TiffCompressionNames: array[TiffCompressionOptionNone..TiffCompressionOptionGroup4] of string = (
|
|
'none', 'lzw', 'rle', 'deflate', 'jpeg', 'g4'
|
|
);
|
|
|
|
{ TCmdLineOptions }
|
|
|
|
constructor TCmdLineOptions.Create;
|
|
begin
|
|
FThresholdLevel := DefaultThreshold;
|
|
FMaxAngle := DefaultMaxAngle;
|
|
FSkipAngle := DefaultSkipAngle;
|
|
FResamplingFilter := rfLinear;
|
|
FThresholdingMethod := tmOtsu;
|
|
FContentRect := Rect(0, 0, 0, 0); // whole page
|
|
FBackgroundColor := $FF000000;
|
|
FOutputFile := SDefaultOutputFile;
|
|
FOperationalFlags := [];
|
|
FShowStats := False;
|
|
FShowParams := False;
|
|
FShowTimings:= False;
|
|
FForcedOutputFormat := ifUnknown;
|
|
FJpegCompressionQuality := -1; // use imaginglib default
|
|
FTiffCompressionScheme := -1; // use imaginglib default
|
|
FFormatSettings := ImagingUtility.GetFormatSettingsForFloats;
|
|
end;
|
|
|
|
function TCmdLineOptions.GetIsValid: Boolean;
|
|
begin
|
|
Result := (InputFile <> '') and (MaxAngle > 0) and (SkipAngle >= 0) and
|
|
((ThresholdingMethod in [tmOtsu]) or (ThresholdingMethod = tmExplicit) and (ThresholdLevel > 0));
|
|
end;
|
|
|
|
function TCmdLineOptions.ParseCommnadLine: Boolean;
|
|
var
|
|
I: LongInt;
|
|
Param, Arg: string;
|
|
|
|
// From Delphi RTL StrUtils.pas - for compiling in Delphi 7
|
|
function SplitString(const S, Delimiters: string): TDynStringArray;
|
|
var
|
|
StartIdx: Integer;
|
|
FoundIdx: Integer;
|
|
SplitPoints: Integer;
|
|
CurrentSplit: Integer;
|
|
I: Integer;
|
|
|
|
function FindDelimiter(const Delimiters, S: string; StartIdx: Integer = 1): Integer;
|
|
var
|
|
Stop: Boolean;
|
|
Len: Integer;
|
|
begin
|
|
Result := 0;
|
|
Len := Length(S);
|
|
Stop := False;
|
|
while (not Stop) and (StartIdx <= Len) do
|
|
if IsDelimiter(Delimiters, S, StartIdx) then
|
|
begin
|
|
Result := StartIdx;
|
|
Stop := True;
|
|
end
|
|
else
|
|
Inc(StartIdx);
|
|
end;
|
|
|
|
begin
|
|
Result := nil;
|
|
if S <> '' then
|
|
begin
|
|
SplitPoints := 0;
|
|
for I := 1 to Length(S) do
|
|
begin
|
|
if IsDelimiter(Delimiters, S, I) then
|
|
Inc(SplitPoints);
|
|
end;
|
|
|
|
SetLength(Result, SplitPoints + 1);
|
|
StartIdx := 1;
|
|
CurrentSplit := 0;
|
|
repeat
|
|
FoundIdx := FindDelimiter(Delimiters, S, StartIdx);
|
|
if FoundIdx <> 0 then
|
|
begin
|
|
Result[CurrentSplit] := Copy(S, StartIdx, FoundIdx - StartIdx);
|
|
Inc(CurrentSplit);
|
|
StartIdx := FoundIdx + 1;
|
|
end;
|
|
until CurrentSplit = SplitPoints;
|
|
Result[SplitPoints] := Copy(S, StartIdx, Length(S) - StartIdx + 1);
|
|
end;
|
|
end;
|
|
|
|
function CheckParam(const Param, Value: string): Boolean;
|
|
var
|
|
StrArray: TDynStringArray;
|
|
ValLower, S: string;
|
|
TempColor: Cardinal;
|
|
Val64: Int64;
|
|
I, J: Integer;
|
|
begin
|
|
Result := True;
|
|
ValLower := LowerCase(Value);
|
|
|
|
if Param = '-o' then
|
|
FOutputFile := Value
|
|
else if Param = '-a' then
|
|
begin
|
|
if not TryStrToFloat(Value, FMaxAngle, FFormatSettings) then
|
|
FErrorMessage := 'Invalid value for max angle parameter: ' + Value;
|
|
end
|
|
else if Param = '-l' then
|
|
begin
|
|
if not TryStrToFloat(Value, FSkipAngle, FFormatSettings) then
|
|
FErrorMessage := 'Invalid value for skip angle parameter: ' + Value;
|
|
end
|
|
else if Param = '-t' then
|
|
begin
|
|
if ValLower = 'a' then
|
|
FThresholdingMethod := tmOtsu
|
|
else
|
|
begin
|
|
FThresholdingMethod := tmExplicit;
|
|
if not TryStrToInt(Value, FThresholdLevel) then
|
|
FErrorMessage := 'Invalid value for treshold parameter: ' + Value;
|
|
end;
|
|
end
|
|
else if Param = '-b' then
|
|
begin
|
|
if TryStrToInt64('$' + ValLower, Val64) then
|
|
begin
|
|
TempColor := Cardinal(Val64 and $FFFFFFFF);
|
|
if TempColor <= $FF then
|
|
begin
|
|
// Just one channel given, replicate for all channels + opaque
|
|
FBackgroundColor := Color32($FF, Byte(TempColor), Byte(TempColor), Byte(TempColor)).Color;
|
|
end
|
|
else if (TempColor <= $FFFFFF) and (Length(ValLower) <= 6) then
|
|
begin
|
|
// RGB given, set alpha to 255 for background
|
|
FBackgroundColor := $FF000000 or TempColor;
|
|
end
|
|
else
|
|
begin
|
|
// Full ARGB given
|
|
FBackgroundColor := TempColor;
|
|
end;
|
|
end
|
|
else
|
|
FErrorMessage := 'Invalid value for background color parameter: ' + Value;
|
|
end
|
|
else if Param = '-f' then
|
|
begin
|
|
if ValLower = 'b1' then
|
|
FForcedOutputFormat := ifBinary
|
|
else if ValLower = 'g8' then
|
|
FForcedOutputFormat := ifGray8
|
|
else if ValLower = 'rgb24' then
|
|
FForcedOutputFormat := ifR8G8B8
|
|
else if ValLower = 'rgba32' then
|
|
FForcedOutputFormat := ifA8R8G8B8
|
|
else
|
|
FErrorMessage := 'Invalid value for format parameter: ' + Value;
|
|
end
|
|
else if Param = '-q' then
|
|
begin
|
|
if ValLower = 'nearest' then
|
|
FResamplingFilter := rfNearest
|
|
else if ValLower = 'linear' then
|
|
FResamplingFilter := rfLinear
|
|
else if ValLower = 'cubic' then
|
|
FResamplingFilter := rfCubic
|
|
else if ValLower = 'lanczos' then
|
|
FResamplingFilter := rfLanczos
|
|
else
|
|
FErrorMessage := 'Invalid value for resampling filter parameter: ' + Value;
|
|
end
|
|
else if Param = '-g' then
|
|
begin
|
|
if Pos('c', ValLower) > 0 then
|
|
Include(FOperationalFlags, ofAutoCrop);
|
|
if Pos('d', ValLower) > 0 then
|
|
Include(FOperationalFlags, ofDetectOnly);
|
|
end
|
|
else if Param = '-s' then
|
|
begin
|
|
if Pos('s', ValLower) > 0 then
|
|
FShowStats := True;
|
|
if Pos('p', ValLower) > 0 then
|
|
FShowParams := True;
|
|
if Pos('t', ValLower) > 0 then
|
|
FShowTimings := True;
|
|
end
|
|
else if Param = '-r' then
|
|
begin
|
|
StrArray := SplitString(ValLower, ',');
|
|
if Length(StrArray) = 4 then
|
|
begin
|
|
FContentRect.Left := StrToInt(StrArray[0]);
|
|
FContentRect.Top := StrToInt(StrArray[1]);
|
|
FContentRect.Right := StrToInt(StrArray[2]);
|
|
FContentRect.Bottom := StrToInt(StrArray[3]);
|
|
end;
|
|
end
|
|
else if Param = '-c' then
|
|
begin
|
|
StrArray := SplitString(ValLower, ',');
|
|
for I := 0 to High(StrArray) do
|
|
begin
|
|
S := StrArray[I];
|
|
if Pos('t', S) = 1 then
|
|
begin
|
|
S := Copy(S, 2);
|
|
FTiffCompressionScheme := -1;
|
|
|
|
for J := Low(TiffCompressionNames) to High(TiffCompressionNames) do
|
|
begin
|
|
if S = TiffCompressionNames[J] then
|
|
begin
|
|
FTiffCompressionScheme := J;
|
|
Break;
|
|
end;
|
|
end;
|
|
|
|
if FTiffCompressionScheme = -1 then
|
|
begin
|
|
FErrorMessage := 'Invalid TIFF output compression spec: ' + S;
|
|
Exit(False);
|
|
end;
|
|
end
|
|
else if Pos('j', S) = 1 then
|
|
begin
|
|
S := Copy(S, 2);
|
|
if not TryStrToInt(S,FJpegCompressionQuality) then
|
|
begin
|
|
FErrorMessage := 'Invalid JPEG output compression spec: ' + S;
|
|
Exit(False);
|
|
end;
|
|
end
|
|
else
|
|
begin
|
|
FErrorMessage := 'Invalid output compression parameter: ' + S;
|
|
Exit(False);
|
|
end;
|
|
end;
|
|
end
|
|
else
|
|
begin
|
|
FErrorMessage := 'Unknown parameter: ' + Param;
|
|
end;
|
|
|
|
if FErrorMessage <> '' then
|
|
Result := False;
|
|
end;
|
|
|
|
begin
|
|
Result := True;
|
|
I := 1;
|
|
|
|
while I <= ParamCount do
|
|
begin
|
|
Param := ParamStr(I);
|
|
if Pos('-', Param) = 1 then
|
|
begin
|
|
Arg := ParamStr(I + 1);
|
|
Inc(I);
|
|
if not CheckParam(Param, Arg) then
|
|
begin
|
|
Result := False;
|
|
Exit;
|
|
end;
|
|
end
|
|
else
|
|
FInputFile := Param;
|
|
|
|
Inc(I);
|
|
end;
|
|
|
|
if FInputFile = '' then
|
|
FErrorMessage := 'No input file given';
|
|
end;
|
|
|
|
function TCmdLineOptions.OptionsToString: string;
|
|
var
|
|
I: Integer;
|
|
CompJpegStr, CompTiffStr, FilterStr, CmdParams: string;
|
|
begin
|
|
CmdParams := '';
|
|
for I := 1 to ParamCount do
|
|
CmdParams := CmdParams + ParamStr(I) + ' ';
|
|
|
|
FilterStr := LowerCase(Copy(TypInfo.GetEnumName(TypeInfo(TResamplingFilter), Integer(FResamplingFilter)), 3));
|
|
CompJpegStr := Iff(JpegCompressionQuality = -1, 'default', IntToStr(JpegCompressionQuality));
|
|
CompTiffStr := 'default';
|
|
if TiffCompressionScheme >= 0 then
|
|
CompTiffStr := TiffCompressionNames[TiffCompressionScheme];
|
|
|
|
Result :=
|
|
'Parameters: ' + CmdParams + sLineBreak +
|
|
' input file = ' + InputFile + sLineBreak +
|
|
' output file = ' + OutputFile + sLineBreak +
|
|
' max angle = ' + FloatToStr(MaxAngle) + sLineBreak +
|
|
' background color = ' + IntToHex(BackgroundColor, 8) + sLineBreak +
|
|
' resampling filter = ' + FilterStr + sLineBreak +
|
|
' thresholding method = ' + Iff(ThresholdingMethod = tmExplicit, 'explicit', 'auto otsu') + sLineBreak +
|
|
' threshold level = ' + IntToStr(ThresholdLevel) + sLineBreak +
|
|
' content rect = ' + Format('%d,%d,%d,%d', [ContentRect.Left, ContentRect.Top, ContentRect.Right, ContentRect.Bottom]) + sLineBreak +
|
|
' output format = ' + Iff(ForcedOutputFormat = ifUnknown, 'default', Imaging.GetFormatName(ForcedOutputFormat)) + sLineBreak +
|
|
' skip angle = ' + FloatToStr(SkipAngle) + sLineBreak +
|
|
' oper flags = ' + Iff(ofAutoCrop in FOperationalFlags, 'auto-crop ', '') + Iff(ofDetectOnly in FOperationalFlags, 'detect-only ', '') + sLineBreak +
|
|
' show info = ' + Iff(ShowParams, 'params ', '') + Iff(ShowStats, 'stats ', '') + Iff(ShowTimings, 'timings ', '') + sLineBreak +
|
|
' output compression = jpeg:' + CompJpegStr + ' tiff:' + CompTiffStr + sLineBreak;
|
|
end;
|
|
|
|
end.
|