Merge ssh://villavu.com:54367/simba
@ -1,3 +1,5 @@
|
||||
.. _mmlref-ocr:
|
||||
|
||||
TMOCR Class
|
||||
===========
|
||||
|
||||
@ -6,6 +8,177 @@ useful functions that can be used to create and identify text. It also contains
|
||||
some functions used in special cases to filter noise. Specifically, these are
|
||||
all the ``Filter*`` functions.
|
||||
|
||||
How the filtering works internally is extensively documented in the code itself
|
||||
and therefore won't be documented here.
|
||||
.. _uptext-filter:
|
||||
|
||||
Uptext
|
||||
------
|
||||
|
||||
To read the UpText, the TMOCR class applies several filters on the client data
|
||||
before performing the actual OCR. We will take a look at the two filters first.
|
||||
|
||||
Filter 1: The Colour Filter
|
||||
~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||
|
||||
We first filter the raw client image with a very rough and tolerant colour
|
||||
comparison / check.
|
||||
We first convert the colour to RGB, and if it falls into the following
|
||||
defined ranges, it may be part of the uptext. We also get the possible
|
||||
shadows.
|
||||
|
||||
|
||||
We will iterate over each pixel in the bitmap, and if it matches any of the
|
||||
*rules* for the colour; we will set it to a constant colour which
|
||||
represents this colour (and corresponding rule). Usually the *base*
|
||||
colour. If it doesn't match any of the rules, it will be painted black.
|
||||
We won't just check for colours, but also for differences between specific
|
||||
R, G, B values. For example, if the colour is white; R, G and B should all
|
||||
lie very close to each other. (That's what makes a colour white.)
|
||||
|
||||
The tolerance for getting the pixels is quite large. The reasons for the
|
||||
high tolerance is because the uptext colour vary quite a lot. They're also
|
||||
transparent and vary thus per background.
|
||||
We will store/match shadow as well; we need it later on in filter 2.
|
||||
|
||||
To my knowledge this algorithm doesn't remove any *valid* points. It does
|
||||
not remove *all* invalid points either; but that is simply not possible
|
||||
based purely on the colour. (If someone has a good idea, let me know)
|
||||
|
||||
In code:
|
||||
|
||||
.. code-block:: pascal
|
||||
|
||||
for y := 0 to bmp.Height - 1 do
|
||||
for x := 0 to bmp.Width - 1 do
|
||||
begin
|
||||
colortorgb(bmp.fastgetpixel(x,y),r,g,b);
|
||||
|
||||
if (r < ocr_Limit_Low) and (g < ocr_Limit_Low) and
|
||||
(b < ocr_Limit_Low) then
|
||||
begin
|
||||
bmp.FastSetPixel(x,y, ocr_Purple);
|
||||
continue;
|
||||
end;
|
||||
|
||||
// Black if no match
|
||||
bmp.fastsetpixel(x,y,0);
|
||||
end;
|
||||
|
||||
Filter 2: The Characteristics Filter
|
||||
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||
|
||||
This second filter is easy to understand but also very powerful:
|
||||
|
||||
- It removes *all* false shadow pixels.
|
||||
- It removes uptext pixels that can't be uptext according to specific
|
||||
rules. These rules are specifically designed so that it will never
|
||||
throw away proper points.
|
||||
|
||||
It also performs another filter right at the start, but we'll disregard that
|
||||
filter for now.
|
||||
|
||||
Removing shadow points is trivial if one understands the following insight.
|
||||
|
||||
If there some pixel is shadow on *x, y*, then it's neighbour *x+1, y+1*
|
||||
may not be a shadow pixel. A shadow is always only one pixel *thick*.
|
||||
|
||||
With this in mind, we can easily define an algorithm which removes all false
|
||||
shadow pixels. In code:
|
||||
|
||||
.. code-block:: pascal
|
||||
|
||||
{
|
||||
The tricky part of the algorithm is that it starts at the bottom,
|
||||
removing shadow point x,y if x-1,y-1 is also shadow. This is
|
||||
more efficient than the obvious way. (It is also easier to implement)
|
||||
}
|
||||
|
||||
for y := bmp.Height - 1 downto 1 do
|
||||
for x := bmp.Width - 1 downto 1 do
|
||||
begin
|
||||
// Is it shadow?
|
||||
if bmp.fastgetpixel(x,y) <> clPurple then
|
||||
continue;
|
||||
// Is the point at x-1,y-1 shadow? If it is
|
||||
// then x, y cannot be shadow.
|
||||
if bmp.fastgetpixel(x,y) = bmp.fastgetpixel(x-1,y-1) then
|
||||
begin
|
||||
bmp.fastsetpixel(x,y,clSilver);
|
||||
continue;
|
||||
end;
|
||||
if bmp.fastgetpixel(x-1,y-1) = 0 then
|
||||
bmp.fastsetpixel(x,y,clSilver);
|
||||
end;
|
||||
|
||||
We are now left with only proper shadow pixels.
|
||||
Now it is time to filter out false Uptext pixels.
|
||||
|
||||
Realize:
|
||||
|
||||
- If *x, y* is uptext, then *x+1, y+1* must be either uptext or shadow.
|
||||
|
||||
In code:
|
||||
|
||||
.. code-block:: pascal
|
||||
|
||||
for y := bmp.Height - 2 downto 0 do
|
||||
for x := bmp.Width - 2 downto 0 do
|
||||
begin
|
||||
if bmp.fastgetpixel(x,y) = clPurple then
|
||||
continue;
|
||||
if bmp.fastgetpixel(x,y) = clBlack then
|
||||
continue;
|
||||
|
||||
// Is the other pixel also uptext?
|
||||
// NOTE THAT IT ALSO HAS TO BE THE SAME COLOUR
|
||||
// UPTEXT IN THIS CASE.
|
||||
// I'm still not sure if this is a good idea or not.
|
||||
// Perhaps it should match *any* uptext colour.
|
||||
if (bmp.fastgetpixel(x,y) = bmp.fastgetpixel(x+1,y+1) ) then
|
||||
continue;
|
||||
|
||||
// If it isn't shadow (and not the same colour uptext, see above)
|
||||
// then it is not uptext.
|
||||
if bmp.fastgetpixel(x+1,y+1) <> clPurple then
|
||||
begin
|
||||
bmp.fastsetpixel(x,y,clOlive);
|
||||
continue;
|
||||
end;
|
||||
|
||||
// If we make it to here, it means the pixel is part of the uptext.
|
||||
end;
|
||||
|
||||
Identifying characters
|
||||
~~~~~~~~~~~~~~~~~~~~~~
|
||||
|
||||
.. note::
|
||||
This part of the documentation is a bit vague and incomplete.
|
||||
|
||||
To actually identify the text we split it up into single character and then
|
||||
pass each character to the OCR engine.
|
||||
|
||||
In the function *getTextPointsIn* we will use both the filters mentioned above.
|
||||
After these have been applied, we will make a bitmap that only contains the
|
||||
shadows as well as a bitmap that only contains the uptext chars (not the
|
||||
shadows)
|
||||
|
||||
Now it is a good idea to count the occurances of all colours
|
||||
(on the character bitmap); we will also use this later on.
|
||||
To split the characters we use the well known *splittpaex* function.
|
||||
|
||||
We will then sort the points for in each character TPA, as this makes
|
||||
makes looping over them and comparing distances easier. We will also
|
||||
calculate the bounding box of each characters TPA.
|
||||
|
||||
.. note::
|
||||
Some more hackery is then used to seperate the characters and find
|
||||
spaces; but isn't yet documented here.
|
||||
|
||||
Normal OCR
|
||||
----------
|
||||
|
||||
.. note::
|
||||
To do :-)
|
||||
A large part is already explained above.
|
||||
Most of the other OCR functions are simply used for plain identifying
|
||||
and have no filtering tasks.
|
||||
|
||||
|
@ -21,3 +21,4 @@ default (Pascal) engine.
|
||||
scriptref/bitmaps.rst
|
||||
scriptref/string.rst
|
||||
scriptref/tpa.rst
|
||||
|
||||
|
@ -1,5 +1,5 @@
|
||||
|
||||
.. _scriptref_bitmaps:
|
||||
.. _scriptref-bitmaps:
|
||||
|
||||
Bitmaps
|
||||
=======
|
||||
|
@ -83,7 +83,7 @@ GetColors
|
||||
|
||||
function GetColors(const Coords : TPointArray) : TIntegerArray;
|
||||
|
||||
GetColor returns the color on the coordinate (x, y) defined by *Coords*.
|
||||
GetColors returns an array of the colours at the given *Coords*.
|
||||
|
||||
|
||||
CountColor
|
||||
|
@ -80,7 +80,7 @@ BitmapFromText
|
||||
|
||||
This function creates a bitmap from a string *text* with the given *font*.
|
||||
For an explanation on how to use and work with Bitmaps, please refer to
|
||||
:ref:`scriptref_bitmaps`.
|
||||
:ref:`scriptref-bitmaps`.
|
||||
|
||||
TPAFromText
|
||||
~~~~~~~~~~~
|
||||
@ -91,7 +91,7 @@ TPAFromText
|
||||
|
||||
This function creates a TPA from a string *text* with the given *font*.
|
||||
For an explanation on how to use and work with TPAs, please refer to
|
||||
:ref:`scriptref_tpointarray`.
|
||||
:ref:`scriptref-tpointarray`.
|
||||
|
||||
TPAFromTextWrap
|
||||
~~~~~~~~~~~~~~~
|
||||
@ -112,7 +112,7 @@ MaskFromText
|
||||
|
||||
This function creates a Mask from a string *text* with the given *font*.
|
||||
For an explanation on how to use and work with TPAs, please refer to
|
||||
:ref:`scriptref_masks`.
|
||||
:ref:`scriptref-masks`.
|
||||
|
||||
Reading Text
|
||||
------------
|
||||
@ -127,6 +127,8 @@ rs_GetUpText
|
||||
This function is a function specific to RuneScape(tm); it reads the text
|
||||
in the upper left corner into a string.
|
||||
|
||||
How these functions actually work can be found here: :ref:`uptext-filter`.
|
||||
|
||||
rs_GetUpTextAt
|
||||
~~~~~~~~~~~~~~
|
||||
|
||||
|
@ -12,6 +12,25 @@ Freeze
|
||||
function Freeze: boolean;
|
||||
|
||||
|
||||
If you call Freeze, the data that is *currently* currently in the client
|
||||
is stored into memory. Simba will then target this memory for all further
|
||||
finding operations; until *Unfreeze* is called. This can dramatically increase
|
||||
speed if you don't care if the image doesn't change. It can be even more
|
||||
important if you don't *want* the image to change; if you want to analyze a
|
||||
specific frame.
|
||||
|
||||
Use like:
|
||||
|
||||
.. code-block:: pascal
|
||||
Freeze;
|
||||
|
||||
if findcolors(...) then
|
||||
...
|
||||
|
||||
Unfreeze
|
||||
|
||||
Make sure you never forget to call Unfreeze!
|
||||
|
||||
Unfreeze
|
||||
--------
|
||||
|
||||
@ -19,6 +38,8 @@ Unfreeze
|
||||
|
||||
function Unfreeze: boolean;
|
||||
|
||||
Unfreeze the client data and restore the original client. See *Freeze* for more
|
||||
details.
|
||||
|
||||
GetClientDimensions
|
||||
-------------------
|
||||
@ -27,6 +48,8 @@ GetClientDimensions
|
||||
|
||||
procedure GetClientDimensions(var w, h:integer);
|
||||
|
||||
Return the size of the client in *w* and *h*.
|
||||
|
||||
|
||||
SetTargetBitmap
|
||||
---------------
|
||||
@ -35,6 +58,8 @@ SetTargetBitmap
|
||||
|
||||
function SetTargetBitmap(Bitmap : integer): integer;
|
||||
|
||||
Set a bitmap as target / client. (It must be loaded by Simba)
|
||||
|
||||
|
||||
SetTargetArray
|
||||
--------------
|
||||
@ -43,6 +68,10 @@ SetTargetArray
|
||||
|
||||
function SetTargetArray(P: Integer; w, h: integer): integer;
|
||||
|
||||
Set a target array as client data. This is generally not something you'd
|
||||
want to call yourself. It is mainly included for external components to allow
|
||||
Simba to efficiently target its memory. See the SMART source on how to do this.
|
||||
|
||||
|
||||
SetEIOSTarget
|
||||
-------------
|
||||
@ -115,6 +144,7 @@ SetDesktopAsClient
|
||||
|
||||
procedure SetDesktopAsClient;
|
||||
|
||||
Set the default desktop as client.
|
||||
|
||||
ActivateClient
|
||||
--------------
|
||||
@ -123,6 +153,8 @@ ActivateClient
|
||||
|
||||
procedure ActivateClient;
|
||||
|
||||
Set the current target as active for key input.
|
||||
|
||||
|
||||
IsTargetValid
|
||||
-------------
|
||||
@ -131,4 +163,5 @@ IsTargetValid
|
||||
|
||||
function IsTargetValid: boolean;
|
||||
|
||||
Returns true if the current target is valid.
|
||||
|
||||
|
299
Extensions/paster.sex
Normal file
@ -0,0 +1,299 @@
|
||||
program Paster;
|
||||
//{$DEFINE DEV}
|
||||
{$IFDEF EXTENSION}
|
||||
var
|
||||
Paster_Menu, GetPaste_MenuItem, Private_MenuItem: TMenuItem;
|
||||
Browser_MenuItem, Paster_MenuItem, AltHost_Menu: TMenuItem;
|
||||
Divider_MenuItems: array[1..2] of TMenuItem;
|
||||
AltHost_Menus: array[1..5] of TMenuItem;
|
||||
AltHost_MenuItems: array[1..5] of array[1..4] of TMenuItem;
|
||||
|
||||
function EncodeString(Data: string): string;
|
||||
var
|
||||
Pattern, Replacement: TStringArray;
|
||||
I: integer;
|
||||
begin
|
||||
Pattern := ['\', #8, #9, #10, #11, #12, #13, '"', {#39,} '/'];
|
||||
Replacement := ['\\', '\b', '\t', '\n', '\v', '\f', '\r', '\"', {'\'#39,} '\/'];
|
||||
Result := Data;
|
||||
if (Length(Pattern) = Length(Replacement)) then
|
||||
for I := Low(Pattern) to High(Pattern) do
|
||||
Result := Replace(Result, Pattern[I], Replacement[I], [rfIgnoreCase, rfReplaceAll]);
|
||||
end;
|
||||
|
||||
function GetName: string;
|
||||
begin;
|
||||
Result := 'Paster';
|
||||
end;
|
||||
|
||||
function GetVersion: string;
|
||||
begin;
|
||||
Result := '0.2a';
|
||||
end;
|
||||
|
||||
function JSONRequest(var Data: string; const HOST, Method: string): boolean;
|
||||
begin
|
||||
{$IFDEF DEV}
|
||||
WriteLn('(HOST, Method) := ('#39 + HOST + #39', '#39 + Method + #39');');
|
||||
WriteLn('Data := '#39 + Data + #39';');
|
||||
{$ENDIF}
|
||||
Data := GetPageEx('http://' + HOST + '/json/?method=' + Method, Data, 'application/json');
|
||||
{$IFDEF DEV}WriteLn('Data := '#39 + Data + #39';');{$ENDIF}
|
||||
if (Data = '') then
|
||||
begin
|
||||
Data := '{"data": null, "error": "Server unresponsive!"}';
|
||||
Result := False;
|
||||
Exit;
|
||||
end;
|
||||
|
||||
if (Method = 'pastes.getPaste') then
|
||||
begin
|
||||
//Little hack for getPaste too work....
|
||||
Data := Replace(Data, '{"data": {', '{', []);
|
||||
Data := Replace(Data, '}, "error": ', ', "error": ', []);
|
||||
end;
|
||||
|
||||
Result := True;
|
||||
end;
|
||||
|
||||
function GetPaste(HOST: string): boolean;
|
||||
var
|
||||
Data: string;
|
||||
begin
|
||||
if (HOST = '') then
|
||||
HOST := 'paste.sheeva.villavu.com';
|
||||
if (InputQuery(GetName + ' ' + GetVersion + ' Extension', 'Which ID would you like too grab?', Data)) then
|
||||
begin
|
||||
if IntToStr(StrToInt(Data)) = Data then
|
||||
Data := '{"paste_id": ' + Data + '}'
|
||||
else
|
||||
Data := '{"paste_id": "' + Data + '"}';
|
||||
|
||||
if (not (JSONRequest(Data, HOST, 'pastes.getPaste'))) then
|
||||
begin
|
||||
WriteLn('[Paster]Error: ' + GetJSONValue(Data, 'error'));
|
||||
Result := False;
|
||||
Exit;
|
||||
end;
|
||||
|
||||
if ((GetJSONValue(Data, 'data') = 'null') and (GetJSONValue(Data, 'error') = 'null')) then
|
||||
begin
|
||||
WriteLn('[Paster]Error: Invalid Paste ID!');
|
||||
Result := False;
|
||||
Exit;
|
||||
end;
|
||||
|
||||
WriteLn('Opening Paste #' + GetJSONValue(Data, 'paste_id') + ' in a new tab!');
|
||||
OpenScript('Paste #' + GetJSONValue(Data, 'paste_id'), GetJSONValue(Data, 'code'));
|
||||
Result := True;
|
||||
end;
|
||||
end;
|
||||
|
||||
function PasteIt(out Data: string; HOST: string): boolean;
|
||||
begin
|
||||
if (HOST = '') then
|
||||
HOST := 'paste.sheeva.villavu.com';
|
||||
if (MessageDlg(GetName + ' ' + GetVersion + ' Extension', 'Upload this script to ' + HOST + '?', mtConfirmation, [mbYes, mbNo], 0) = mrYes) then
|
||||
begin
|
||||
Data := '{"language": "delphi", "code": "' + EncodeString(ScriptText) + '", "private": ' + Lowercase(BoolToStr(Private_MenuItem.Checked)) + '}';
|
||||
JSONRequest(Data, HOST, 'pastes.newPaste');
|
||||
if (GetJSONValue(Data, 'error') = 'null') then
|
||||
begin
|
||||
Data := GetJSONValue(Data, 'data');
|
||||
Result := True;
|
||||
end else
|
||||
Data := '[Paster]Error: ' + GetJSONValue(Data, 'error');
|
||||
end;
|
||||
end;
|
||||
|
||||
procedure Paste(Host: string);
|
||||
var
|
||||
Data: string;
|
||||
begin
|
||||
if PasteIt(Data, Host) then
|
||||
begin
|
||||
if (Browser_MenuItem.Checked) then
|
||||
begin
|
||||
WriteLn('Opening pasted script at "http://' + HOST + '/show/' + Data + '/"!');
|
||||
OpenWebPage('http://' + HOST + '/show/' + Data + '/');
|
||||
end else
|
||||
WriteLn('Script pasted at id "' + Data + '"');
|
||||
end else
|
||||
WriteLn(Data);
|
||||
end;
|
||||
|
||||
procedure UpdateHost(I: integer);
|
||||
var
|
||||
Data: string;
|
||||
begin
|
||||
if InputQuery(GetName + ' ' + GetVersion + ' Extension', 'Please input the LodgeIt Host! (Ex: paste.pocoo.org)', Data) then
|
||||
begin
|
||||
{$IFDEF DEV}WriteLn('Data := '#39 + Data + #39';');{$ENDIF}
|
||||
if (Data = '') then
|
||||
if (AltHost_Menus[I].Caption = 'Host ' + IntToStr(I)) then
|
||||
begin
|
||||
WriteLn('[Paster]Error: The host cannot be blank!');
|
||||
Exit;
|
||||
end else
|
||||
begin
|
||||
AltHost_Menus[I].Caption := 'Host ' + IntToStr(I);
|
||||
AltHost_MenuItems[I][1].Enabled := False;
|
||||
AltHost_MenuItems[I][2].Enabled := False;
|
||||
Settings.setKeyValue('Host' + IntToStr(I), 'Host ' + IntToStr(I));
|
||||
Exit;
|
||||
end;
|
||||
AltHost_Menus[I].Caption := Data;
|
||||
AltHost_MenuItems[I][1].Enabled := True;
|
||||
AltHost_MenuItems[I][2].Enabled := True;
|
||||
Settings.setKeyValue('Host' + IntToStr(I), Data);
|
||||
end;
|
||||
end;
|
||||
|
||||
procedure OnClick(Sender: TObject);
|
||||
var
|
||||
I, K: integer;
|
||||
begin;
|
||||
case Sender of
|
||||
Paster_MenuItem: Paste('');
|
||||
GetPaste_MenuItem: GetPaste('');
|
||||
Private_MenuItem: begin
|
||||
Private_MenuItem.Checked := (not (Private_MenuItem.Checked));
|
||||
Settings.setKeyValue('Private', Lowercase(BoolToStr(Private_MenuItem.Checked)));
|
||||
{$IFDEF DEV}WriteLn('Private = ' + Lowercase(BoolToStr(Private_MenuItem.Checked)));{$ENDIF}
|
||||
end;
|
||||
Browser_MenuItem: begin
|
||||
Browser_MenuItem.Checked := (not (Browser_MenuItem.Checked));
|
||||
Settings.setKeyValue('OpenBrowser', Lowercase(BoolToStr(Browser_MenuItem.Checked)));
|
||||
{$IFDEF DEV}WriteLn('OpenBrowser = ' + Lowercase(BoolToStr(Browser_MenuItem.Checked)));{$ENDIF}
|
||||
end;
|
||||
end;
|
||||
for I := 1 to 5 do
|
||||
for K := 1 to 4 do
|
||||
if (Sender = AltHost_MenuItems[I][K]) then
|
||||
begin
|
||||
{$IFDEF DEV}WriteLn('Sender = ' + IntToStr(I) + ', ' + IntToStr(K));{$ENDIF}
|
||||
if (K = 4) then
|
||||
UpdateHost(I);
|
||||
if (K = 2) then
|
||||
GetPaste(Settings.getKeyValue('Host' + IntToStr(I)));
|
||||
if (K = 1) then
|
||||
Paste(Settings.getKeyValue('Host' + IntToStr(I)));
|
||||
Break;
|
||||
end;
|
||||
end;
|
||||
|
||||
procedure Attach;
|
||||
begin;
|
||||
WriteLn(GetName + ' ' + GetVersion + ' Plugin Loaded!');
|
||||
Paster_Menu.Visible := True;
|
||||
end;
|
||||
|
||||
procedure Detach;
|
||||
begin
|
||||
Paster_Menu.Visible := False;
|
||||
end;
|
||||
|
||||
procedure Free;
|
||||
var
|
||||
I, K: integer;
|
||||
begin
|
||||
for I := 5 downto 1 do
|
||||
begin
|
||||
for K := 4 downto 1 do
|
||||
AltHost_MenuItems[I][K].Free;
|
||||
AltHost_Menus[I].Free;
|
||||
end;
|
||||
AltHost_Menu.Free;
|
||||
Private_MenuItem.Free;
|
||||
for I := 2 downto 1 do
|
||||
Divider_MenuItems[I].Free;
|
||||
Paster_MenuItem.Free;
|
||||
Paster_Menu.Free;
|
||||
end;
|
||||
|
||||
procedure init;
|
||||
var
|
||||
I, K: integer;
|
||||
begin;
|
||||
Paster_Menu := TMenuItem.Create(Simba_MainMenu);
|
||||
Paster_Menu.Caption := GetName;
|
||||
Simba_MainMenu.Items.Add(Paster_Menu);
|
||||
|
||||
Paster_MenuItem := TMenuItem.Create(Paster_Menu);
|
||||
with Paster_MenuItem do
|
||||
begin
|
||||
Caption := 'Paste It!';
|
||||
OnClick := @OnClick;
|
||||
end;
|
||||
Paster_Menu.Add(Paster_MenuItem);
|
||||
|
||||
GetPaste_MenuItem := TMenuItem.Create(Paster_Menu);
|
||||
with GetPaste_MenuItem do
|
||||
begin
|
||||
Caption := 'Get Paste!';
|
||||
OnClick := @OnClick;
|
||||
end;
|
||||
Paster_Menu.Add(GetPaste_MenuItem);
|
||||
|
||||
for I := 1 to 2 do
|
||||
begin
|
||||
Divider_MenuItems[I] := TMenuItem.Create(Paster_Menu);
|
||||
Divider_MenuItems[I].Caption := '-';
|
||||
end;
|
||||
|
||||
Paster_Menu.Add(Divider_MenuItems[1]);
|
||||
|
||||
Private_MenuItem := TMenuItem.Create(Paster_Menu);
|
||||
with Private_MenuItem do
|
||||
begin
|
||||
Caption := 'Private';
|
||||
OnClick := @OnClick;
|
||||
Checked := (Lowercase(Settings.getKeyValueDef('Private', 'true')) = 'true');
|
||||
end;
|
||||
Paster_Menu.Add(Private_MenuItem);
|
||||
|
||||
Browser_MenuItem := TMenuItem.Create(Paster_Menu);
|
||||
with Browser_MenuItem do
|
||||
begin
|
||||
Caption := 'Open in Browser';
|
||||
OnClick := @OnClick;
|
||||
Checked := (Lowercase(Settings.getKeyValueDef('OpenBrowser', 'true')) = 'true');
|
||||
end;
|
||||
Paster_Menu.Add(Browser_MenuItem);
|
||||
|
||||
Paster_Menu.Add(Divider_MenuItems[2]);
|
||||
|
||||
AltHost_Menu := TMenuItem.Create(Paster_Menu);
|
||||
AltHost_Menu.Caption := 'Alternate Hosts';
|
||||
Paster_Menu.Add(AltHost_Menu);
|
||||
|
||||
for I := 1 to 5 do
|
||||
begin
|
||||
AltHost_Menus[I] := TMenuItem.Create(AltHost_Menu);
|
||||
AltHost_Menus[I].Caption := Settings.getKeyValueDef('Host' + IntToStr(I), 'Host ' + IntToStr(I));
|
||||
AltHost_Menu.Add(AltHost_Menus[I]);
|
||||
|
||||
for K := 1 to 4 do
|
||||
begin
|
||||
AltHost_MenuItems[I][K] := TMenuItem.Create(AltHost_Menus[I]);
|
||||
case K of
|
||||
1: AltHost_MenuItems[I][1].Caption := 'Paste It!';
|
||||
2: AltHost_MenuItems[I][2].Caption := 'Get Paste!';
|
||||
3: AltHost_MenuItems[I][3].Caption := '-';
|
||||
4: AltHost_MenuItems[I][4].Caption := 'Update Host';
|
||||
end;
|
||||
AltHost_MenuItems[I][K].OnClick := @OnClick;
|
||||
if ((not ((K = 3) or (K = 4))) and (AltHost_Menus[I].Caption = 'Host ' + IntToStr(I))) then
|
||||
AltHost_MenuItems[I][K].Enabled := False;
|
||||
|
||||
AltHost_Menus[I].Add(AltHost_MenuItems[I][K]);
|
||||
end;
|
||||
end;
|
||||
end;
|
||||
{$ENDIF}
|
||||
begin
|
||||
{$IFNDEF EXTENSION}
|
||||
WriteLn('This is a Extension for Simba. If you are in Simba click View->Extensions and enable paster.sex!');
|
||||
{$ENDIF}
|
||||
end.
|
||||
|
Before Width: | Height: | Size: 486 B After Width: | Height: | Size: 488 B |
Before Width: | Height: | Size: 486 B After Width: | Height: | Size: 488 B |
Before Width: | Height: | Size: 342 B After Width: | Height: | Size: 344 B |
Before Width: | Height: | Size: 486 B After Width: | Height: | Size: 488 B |
Before Width: | Height: | Size: 486 B After Width: | Height: | Size: 488 B |
Before Width: | Height: | Size: 270 B After Width: | Height: | Size: 272 B |
Before Width: | Height: | Size: 270 B After Width: | Height: | Size: 272 B |
Before Width: | Height: | Size: 486 B After Width: | Height: | Size: 488 B |
Before Width: | Height: | Size: 270 B After Width: | Height: | Size: 272 B |
Before Width: | Height: | Size: 702 B After Width: | Height: | Size: 704 B |
Before Width: | Height: | Size: 558 B After Width: | Height: | Size: 560 B |
Before Width: | Height: | Size: 486 B After Width: | Height: | Size: 488 B |
Before Width: | Height: | Size: 486 B After Width: | Height: | Size: 488 B |
Before Width: | Height: | Size: 486 B After Width: | Height: | Size: 488 B |
Before Width: | Height: | Size: 486 B After Width: | Height: | Size: 488 B |
Before Width: | Height: | Size: 414 B After Width: | Height: | Size: 416 B |
Before Width: | Height: | Size: 342 B After Width: | Height: | Size: 344 B |
Before Width: | Height: | Size: 558 B After Width: | Height: | Size: 560 B |
Before Width: | Height: | Size: 486 B After Width: | Height: | Size: 488 B |
Before Width: | Height: | Size: 630 B After Width: | Height: | Size: 632 B |
Before Width: | Height: | Size: 486 B After Width: | Height: | Size: 488 B |
Before Width: | Height: | Size: 558 B After Width: | Height: | Size: 560 B |
Before Width: | Height: | Size: 486 B After Width: | Height: | Size: 488 B |
Before Width: | Height: | Size: 486 B After Width: | Height: | Size: 488 B |
Before Width: | Height: | Size: 270 B After Width: | Height: | Size: 272 B |
Before Width: | Height: | Size: 270 B After Width: | Height: | Size: 272 B |
Before Width: | Height: | Size: 270 B After Width: | Height: | Size: 272 B |
Before Width: | Height: | Size: 342 B After Width: | Height: | Size: 416 B |
Before Width: | Height: | Size: 270 B After Width: | Height: | Size: 272 B |
BIN
Fonts/CharsNPC/48.bmp
Normal file
After Width: | Height: | Size: 704 B |
BIN
Fonts/CharsNPC/49.bmp
Normal file
After Width: | Height: | Size: 488 B |
BIN
Fonts/CharsNPC/50.bmp
Normal file
After Width: | Height: | Size: 704 B |
BIN
Fonts/CharsNPC/51.bmp
Normal file
After Width: | Height: | Size: 704 B |
BIN
Fonts/CharsNPC/52.bmp
Normal file
After Width: | Height: | Size: 632 B |
BIN
Fonts/CharsNPC/53.bmp
Normal file
After Width: | Height: | Size: 704 B |
BIN
Fonts/CharsNPC/54.bmp
Normal file
After Width: | Height: | Size: 704 B |
BIN
Fonts/CharsNPC/55.bmp
Normal file
After Width: | Height: | Size: 632 B |
BIN
Fonts/CharsNPC/56.bmp
Normal file
After Width: | Height: | Size: 704 B |
BIN
Fonts/CharsNPC/57.bmp
Normal file
After Width: | Height: | Size: 704 B |
Before Width: | Height: | Size: 486 B After Width: | Height: | Size: 488 B |
Before Width: | Height: | Size: 558 B After Width: | Height: | Size: 560 B |
Before Width: | Height: | Size: 702 B After Width: | Height: | Size: 704 B |
Before Width: | Height: | Size: 630 B After Width: | Height: | Size: 632 B |
Before Width: | Height: | Size: 774 B After Width: | Height: | Size: 776 B |
Before Width: | Height: | Size: 702 B After Width: | Height: | Size: 704 B |
Before Width: | Height: | Size: 630 B After Width: | Height: | Size: 632 B |
Before Width: | Height: | Size: 702 B After Width: | Height: | Size: 704 B |
Before Width: | Height: | Size: 702 B After Width: | Height: | Size: 704 B |
Before Width: | Height: | Size: 486 B After Width: | Height: | Size: 488 B |
Before Width: | Height: | Size: 486 B After Width: | Height: | Size: 488 B |
Before Width: | Height: | Size: 702 B After Width: | Height: | Size: 704 B |
Before Width: | Height: | Size: 558 B After Width: | Height: | Size: 560 B |
Before Width: | Height: | Size: 918 B After Width: | Height: | Size: 920 B |
Before Width: | Height: | Size: 846 B After Width: | Height: | Size: 848 B |
Before Width: | Height: | Size: 702 B After Width: | Height: | Size: 704 B |
Before Width: | Height: | Size: 702 B After Width: | Height: | Size: 704 B |
Before Width: | Height: | Size: 702 B After Width: | Height: | Size: 704 B |
Before Width: | Height: | Size: 702 B After Width: | Height: | Size: 704 B |
Before Width: | Height: | Size: 558 B After Width: | Height: | Size: 560 B |
Before Width: | Height: | Size: 702 B After Width: | Height: | Size: 704 B |
Before Width: | Height: | Size: 702 B After Width: | Height: | Size: 704 B |
Before Width: | Height: | Size: 702 B After Width: | Height: | Size: 704 B |
Before Width: | Height: | Size: 918 B After Width: | Height: | Size: 920 B |
Before Width: | Height: | Size: 920 B After Width: | Height: | Size: 920 B |
Before Width: | Height: | Size: 702 B After Width: | Height: | Size: 704 B |
Before Width: | Height: | Size: 630 B After Width: | Height: | Size: 632 B |
Before Width: | Height: | Size: 486 B After Width: | Height: | Size: 488 B |
Before Width: | Height: | Size: 486 B After Width: | Height: | Size: 488 B |
Before Width: | Height: | Size: 486 B After Width: | Height: | Size: 488 B |
Before Width: | Height: | Size: 462 B After Width: | Height: | Size: 464 B |
@ -6,4 +6,5 @@ Linux:
|
||||
* IsKeyDown doesn't work yet.
|
||||
|
||||
Windows:
|
||||
* Interpreter doesn't work well on x64.
|
||||
|
||||
|
BIN
Plugins/libsmart.dll
Normal file → Executable file
@ -64,7 +64,7 @@ begin
|
||||
AboutMemo.Lines.Add('Simba is released under the GPL license.');
|
||||
AboutMemo.Lines.Add(format('You are currently using version: %d',[SimbaUnit.SimbaVersion]));
|
||||
AboutMemo.Lines.Add('');
|
||||
AboutMemo.Lines.Add('Please report bugs at: http://mufasa.villavu.com/mantis/');
|
||||
AboutMemo.Lines.Add('Please report bugs at: http://bugs.villavu.com/');
|
||||
end;
|
||||
|
||||
procedure TAboutForm.OkButtonClick(Sender: TObject);
|
||||
|
@ -17,7 +17,7 @@ object ScriptFrame: TScriptFrame
|
||||
Font.Height = -13
|
||||
Font.Name = 'Courier New'
|
||||
Font.Pitch = fpFixed
|
||||
Font.Quality = fqNonAntialiased
|
||||
Font.Quality = fqProof
|
||||
ParentColor = False
|
||||
ParentFont = False
|
||||
PopupMenu = SimbaForm.ScriptPopup
|
||||
@ -28,7 +28,7 @@ object ScriptFrame: TScriptFrame
|
||||
OnKeyPress = SynEditKeyPress
|
||||
OnClickLink = SynEditClickLink
|
||||
OnMouseLink = SynEditMouseLink
|
||||
Gutter.Width = 57
|
||||
Gutter.Width = 59
|
||||
Gutter.MouseActions = <
|
||||
item
|
||||
Shift = []
|
||||
@ -627,7 +627,7 @@ object ScriptFrame: TScriptFrame
|
||||
Width = 24
|
||||
end
|
||||
object TSynGutterLineNumber
|
||||
Width = 17
|
||||
Width = 19
|
||||
MouseActions = <>
|
||||
MarkupInfo.Background = clBtnFace
|
||||
MarkupInfo.Foreground = clNone
|
||||
|
@ -66,6 +66,8 @@ uses
|
||||
tpa, //Tpa stuff
|
||||
SynRegExpr,
|
||||
lclintf,
|
||||
httpsend,
|
||||
superobject,
|
||||
SimbaUnit,updateform, mmisc, mmlpsthread; // for GetTickCount and others.//Writeln
|
||||
|
||||
{$ifdef Linux}
|
||||
@ -181,7 +183,11 @@ begin
|
||||
AddFunction(@ext_UnTarEx,'function UnTarEx(const Input : string;const outputdir : string; overwrite : boolean): boolean;');
|
||||
AddFunction(@ext_MessageDlg,'function MessageDlg(const aCaption, aMsg: string; DlgType: TMsgDlgType;Buttons: TMsgDlgButtons; HelpCtx: Longint): Integer;');
|
||||
AddFunction(@ext_InputQuery,'function InputQuery(const ACaption, APrompt : String; var Value : String) : Boolean;');
|
||||
AddFunction(@ext_ScriptText,'function ScriptText: string;');
|
||||
AddFunction(@ext_OpenScript,'procedure OpenScript(Name, Data: string);');
|
||||
AddRegisteredPTRVariable('Settings','TMMLSettingsSandbox');
|
||||
AddFunction(@ext_GetPageEx,'function GetPageEx(const URL, PostData, MimeType: string): string;');
|
||||
AddFunction(@ext_GetJSONValue,'function GetJSONValue(const Data, Value: string): string;');
|
||||
AddRegisteredVariable('Simba','TForm');
|
||||
AddRegisteredVariable('Simba_MainMenu','TMainMenu');
|
||||
AddRegisteredVariable('Client','TClient');
|
||||
|
@ -45,7 +45,7 @@ uses
|
||||
CastaliaSimplePasPar, v_AutoCompleteForm, PSDump, settings, updater;
|
||||
|
||||
const
|
||||
SimbaVersion = 715;
|
||||
SimbaVersion = 720;
|
||||
|
||||
interp_PS = 0; //PascalScript
|
||||
interp_RT = 1; //RUTIS
|
||||
|
@ -122,3 +122,53 @@ function ext_InputQuery(const ACaption, APrompt : String; var Value : String) :
|
||||
begin
|
||||
result := InputQuery(acaption,aprompt,value);
|
||||
end;
|
||||
|
||||
function ext_ScriptText: string;
|
||||
begin
|
||||
Result := SimbaForm.CurrScript.SynEdit.Lines.Text;
|
||||
Result := ReplaceRegExpr('([N|n][A|a][M|m][E|e]|[P|p][A|a][S|s]{2}|[P|p][I|i][N|n])\s*\:\=\s*\''.*?\'';', Result, '$1 := ''*********'';', True);
|
||||
end;
|
||||
|
||||
procedure ext_OpenScript(Name, Data: string);
|
||||
begin
|
||||
if (Name = '') then
|
||||
Name := 'Untitled';
|
||||
SimbaForm.AddTab;
|
||||
SimbaForm.CurrScript.SynEdit.Lines.Text := Data;
|
||||
SimbaForm.CurrScript.ScriptName := Name;
|
||||
SimbaForm.RefreshTab;
|
||||
SimbaForm.UpdateTitle;
|
||||
end;
|
||||
|
||||
function ext_GetPageEx(const URL, PostData, MimeType: string): string;
|
||||
var
|
||||
HTTPSend: THTTPSend;
|
||||
begin
|
||||
HTTPSend := THTTPSend.Create;
|
||||
try
|
||||
HTTPSend.MimeType := MimeType;
|
||||
HTTPSend.Document.Clear;
|
||||
HTTPSend.Document.Write(PostData[1], Length(PostData));
|
||||
try
|
||||
if HTTPSend.HTTPMethod('POST', URL) then
|
||||
begin;
|
||||
SetLength(Result, HTTPSend.Document.Size);
|
||||
HTTPSend.Document.Read(Result[1], Length(Result));
|
||||
end else
|
||||
Result := '';
|
||||
except
|
||||
on e : exception do
|
||||
mDebugLn('Exception in GetPage in Extensions: ' + e.message);
|
||||
end;
|
||||
finally
|
||||
HTTPSend.Free;
|
||||
end;
|
||||
end;
|
||||
|
||||
function ext_GetJSONValue(const Data, Value: string): string;
|
||||
var
|
||||
SuperObject: ISuperObject;
|
||||
begin
|
||||
SuperObject := SO(Data);
|
||||
Result := SuperObject.AsObject.S[Value];
|
||||
end;
|
||||
|
@ -3,6 +3,11 @@ begin
|
||||
Result := CurrThread.Client.MOCR.GetUpTextAtEx(7, 7, true);
|
||||
end;
|
||||
|
||||
function ps_rs_GetUpTextAtEx(x, y: integer; shadow: boolean): string; extdecl;
|
||||
begin
|
||||
result := CurrThread.Client.MOCR.GetUpTextAtEx(x, y, shadow);
|
||||
end;
|
||||
|
||||
function ps_rs_GetUpTextAt(x, y : integer): string; extdecl;
|
||||
begin
|
||||
result := CurrThread.Client.MOCR.GetUpTextAtEx(x,y,true);
|
||||
|
@ -316,6 +316,7 @@ AddFunction(@ps_GetKeyCode,'function GetKeyCode(c : char) : integer;');
|
||||
SetCurrSection('OCR');
|
||||
AddFunction(@ps_rs_GetUpText, 'function rs_GetUpText: string;');
|
||||
AddFunction(@ps_rs_GetUpTextAt, 'function rs_GetUpTextAt(x, y : integer): string;');
|
||||
AddFunction(@ps_rs_GetUpTextAtEx, 'function rs_GetUpTextAtEx(x, y: integer; shadow: boolean): string');
|
||||
AddFunction(@ps_BitmapFromText, 'function BitmapFromText(const text, font: String): integer;');
|
||||
AddFunction(@ps_TPAFromText, 'function TPAFromText(const text, font: String;var w,h : integer): TPointArray;');
|
||||
AddFunction(@ps_TPAFromTextWrap,'procedure TPAFromTextWrap(const text, font: String;var w,h : integer;var TPA : TPointArray);');
|
||||
|
@ -202,6 +202,7 @@ function TMFonts.LoadFont(const Name: String; Shadow: Boolean): boolean;
|
||||
var
|
||||
f: TMFont;
|
||||
begin
|
||||
Result := True;
|
||||
if not DirectoryExists(FPath + Name) then
|
||||
begin
|
||||
raise Exception.Create('LoadFont: Directory ' + FPath + Name + ' does not exists.');
|
||||
|
@ -23,6 +23,7 @@ interface
|
||||
procedure FreePlugins;
|
||||
procedure LoadPluginsDir(DirIndex : integer);
|
||||
function VerifyPath(Path : string) : string;
|
||||
function LoadPluginNoFallback(PluginName : string) : integer;
|
||||
protected
|
||||
function InitPlugin(plugin: TLibHandle): boolean; virtual; abstract;
|
||||
public
|
||||
@ -113,8 +114,7 @@ implementation
|
||||
end;
|
||||
end;
|
||||
|
||||
|
||||
function TGenericLoader.LoadPlugin(PluginName: string): Integer;
|
||||
function TGenericLoader.LoadPluginNoFallback(PluginName: string): Integer;
|
||||
var
|
||||
i, ii : integer;
|
||||
PlugExt: String = {$IFDEF LINUX}'.so';{$ELSE}'.dll';{$ENDIF}
|
||||
@ -151,6 +151,19 @@ implementation
|
||||
end;
|
||||
|
||||
|
||||
function TGenericLoader.LoadPlugin(PluginName: string): Integer;
|
||||
var
|
||||
CpuBits: String = {$IFDEF CPU32}'32';{$ELSE}'64';{$ENDIF}
|
||||
begin
|
||||
try
|
||||
Result := LoadPluginNoFallback(PluginName);
|
||||
except
|
||||
on exception do Result:= LoadPluginNoFallback(PluginName+CpuBits);
|
||||
end;
|
||||
|
||||
end;
|
||||
|
||||
|
||||
constructor TGenericLoader.Create;
|
||||
begin
|
||||
inherited Create;
|
||||
|
@ -52,7 +52,7 @@ type
|
||||
function GetUpTextAtEx(atX, atY: integer; shadow: boolean): string;
|
||||
function GetUpTextAt(atX, atY: integer; shadow: boolean): string;
|
||||
|
||||
procedure FilterUpTextByColour(bmp: TMufasaBitmap; w,h: integer);
|
||||
procedure FilterUpTextByColour(bmp: TMufasaBitmap);
|
||||
procedure FilterUpTextByCharacteristics(bmp: TMufasaBitmap; w,h: integer);
|
||||
procedure FilterShadowBitmap(bmp: TMufasaBitmap);
|
||||
procedure FilterCharsBitmap(bmp: TMufasaBitmap);
|
||||
@ -197,84 +197,124 @@ end;
|
||||
Non optimised. We can make it use direct data instead of fastgetpixel and
|
||||
fastsetpixel, but speed isn't really an issue. The entire algorithm is still
|
||||
fast enough.
|
||||
|
||||
There are some issues with this method; since later on the algorithm
|
||||
(if I recall correctly) throws aways some pixels if a character isn't just
|
||||
one colour)
|
||||
|
||||
We will match shadow as well; we need it later on.
|
||||
}
|
||||
|
||||
procedure TMOCR.FilterUpTextByColour(bmp: TMufasaBitmap; w,h: integer);
|
||||
procedure TMOCR.FilterUpTextByColour(bmp: TMufasaBitmap);
|
||||
var
|
||||
x, y,r, g, b: Integer;
|
||||
begin
|
||||
// We're going to filter the bitmap solely on colours first.
|
||||
// If we found one, we set it to it's `normal' colour.
|
||||
{
|
||||
We're going to filter the bitmap solely on colours first.
|
||||
If we found one, we set it to it's `normal' colour.
|
||||
|
||||
Note that these values aren't randomly chosen, but I didn't spent too
|
||||
much time on finding the exact values either.
|
||||
|
||||
We will iterate over each pixel in the bitmap, and if it matches any of the
|
||||
``rules'' for the colour; we will set it to a constant colour which
|
||||
represents this colour (and corresponding rule). Usually the ``base''
|
||||
colour.
|
||||
|
||||
If it doesn't match any rule, we can safely make the pixel black.
|
||||
|
||||
To my knowledge this algorithm doesn't remove any valid points. It does
|
||||
not remove *all* invalid points either; but that is simply not possible
|
||||
based purely on the colour. (If someone has a good idea, let me know)
|
||||
}
|
||||
for y := 0 to bmp.Height - 1 do
|
||||
for x := 0 to bmp.Width - 1 do
|
||||
begin
|
||||
colortorgb(bmp.fastgetpixel(x,y),r,g,b);
|
||||
// the abs(g-b) < 15 seems to help heaps when taking out crap points
|
||||
{
|
||||
abs(g-b) < 15 seems to help heaps when taking out invalid (white) points
|
||||
obviously if a colour is ``white'' R, G B should all be approx the
|
||||
same value. That is what the last part of the if statement checks.
|
||||
}
|
||||
if (r > ocr_Limit_High) and (g > ocr_Limit_High) and (b > ocr_Limit_High)
|
||||
// 50 or 55. 55 seems to be better.
|
||||
and (abs(r-g) + abs(r-b) + abs(g-b) < 55) then
|
||||
// TODO: make 55 a var, and make it so that it can be set
|
||||
begin
|
||||
bmp.fastsetpixel(x,y,ocr_White);
|
||||
continue;
|
||||
end;
|
||||
|
||||
{ Quite straightforward and works quite well, afaik }
|
||||
if (r < ocr_Limit_Low) and (g > ocr_Limit_High) and (b > ocr_Limit_High) then
|
||||
begin
|
||||
bmp.fastsetpixel(x,y,ocr_Blue);
|
||||
continue;
|
||||
end;
|
||||
|
||||
{ Ditto }
|
||||
if (r < ocr_Limit_Low) and (g > ocr_Limit_High) and (b < ocr_Limit_Low) then
|
||||
begin
|
||||
bmp.fastsetpixel(x,y,ocr_Green);
|
||||
continue;
|
||||
end;
|
||||
|
||||
// false results with fire
|
||||
{ False results with fire }
|
||||
if(r > ocr_Limit_High) and (g > 100) and (g < ocr_Limit_High) and (b > 40) and (b < 127) then
|
||||
begin
|
||||
bmp.fastsetpixel(x,y,ocr_ItemC);
|
||||
continue;
|
||||
end;
|
||||
|
||||
{ Works fine afaik }
|
||||
if(r > ocr_Limit_High) and (g > ocr_Limit_High) and (b < ocr_Limit_Low) then
|
||||
begin
|
||||
bmp.fastsetpixel(x,y,ocr_Yellow);
|
||||
continue;
|
||||
end;
|
||||
// better use g < 40 than ocr_Limit_Low imo
|
||||
|
||||
// better use g < 40 than ocr_Limit_Low imo (TODO)
|
||||
if (r > ocr_Limit_High) and (g < ocr_Limit_Low) and (b < ocr_Limit_Low) then
|
||||
begin
|
||||
bmp.fastsetpixel(x,y,ocr_Red);
|
||||
continue;
|
||||
end;
|
||||
|
||||
{ Red as well }
|
||||
if (r > ocr_Limit_High) and (g > ocr_Limit_Low) and (b < ocr_Limit_Low) then
|
||||
begin
|
||||
bmp.fastsetpixel(x,y,ocr_Red);
|
||||
continue;
|
||||
end;
|
||||
|
||||
if (r > ocr_Limit_Med) and (r < (ocr_Limit_High + 10)) and (g > ocr_Limit_Low - 10) and
|
||||
(b < 20) then
|
||||
begin
|
||||
bmp.fastsetpixel(x,y,ocr_Green);
|
||||
continue;
|
||||
end;
|
||||
//shadow
|
||||
begin
|
||||
bmp.fastsetpixel(x,y,ocr_Green);
|
||||
continue;
|
||||
end;
|
||||
|
||||
// Match Shadow as well. We will need it later.
|
||||
if (r < ocr_Limit_Low) and (g < ocr_Limit_Low) and (b < ocr_Limit_Low) then
|
||||
begin
|
||||
bmp.FastSetPixel(x,y, ocr_Purple);
|
||||
continue;
|
||||
end;
|
||||
|
||||
// Black if no match
|
||||
bmp.fastsetpixel(x,y,0);
|
||||
end;
|
||||
|
||||
|
||||
// make outline black for shadow characteristics filter
|
||||
// first and last horiz line = 0
|
||||
{
|
||||
Make outline black for shadow characteristics filter
|
||||
First and last horiz line = 0. This makes using the shadow algo easier.
|
||||
We don't really throw away information either since we already took the
|
||||
bitmap a bit larger than required.
|
||||
}
|
||||
for x := 0 to bmp.width -1 do
|
||||
bmp.fastsetpixel(x,0,0);
|
||||
for x := 0 to bmp.width -1 do
|
||||
bmp.fastsetpixel(x,bmp.height-1,0);
|
||||
// same for vertical lines
|
||||
|
||||
// Same for vertical lines
|
||||
for y := 0 to bmp.Height -1 do
|
||||
bmp.fastsetpixel(0, y, 0);
|
||||
for y := 0 to bmp.Height -1 do
|
||||
@ -288,7 +328,6 @@ end;
|
||||
on characteristics.
|
||||
|
||||
For the uptext, a few things apply...
|
||||
First of all:
|
||||
|
||||
*** Remove False Shadow ***
|
||||
if shadow[x,y] then not shadow[x-1,y-1]
|
||||
@ -300,7 +339,6 @@ end;
|
||||
will soon see that this algorithm will be a *lot* more efficient if we
|
||||
start at the right bottom, instead of the left top. Which means we should
|
||||
work with x-1 and y-1, rather than x+1,y+1
|
||||
Yeah.... My comments are vague.
|
||||
)
|
||||
|
||||
*** UpText chars identity 1 and 2 ***
|
||||
@ -314,11 +352,14 @@ procedure TMOCR.FilterUpTextByCharacteristics(bmp: TMufasaBitmap; w,h: integer);
|
||||
var
|
||||
x,y: Integer;
|
||||
begin
|
||||
// Filter 2
|
||||
// This performs a `simple' filter.
|
||||
// What we are doing here is simple checking that if Colour[x,y] is part
|
||||
// of the uptext, then so must Colour[x+1,y+1], or Colour[x+1,y+1] is a shadow.
|
||||
// if it is neither, we can safely remove it.
|
||||
{ Filter 2
|
||||
This performs a `simple' filter.
|
||||
What we are doing here is simple checking that if Colour[x,y] is part
|
||||
of the uptext, then so must Colour[x+1,y+1], or Colour[x+1,y+1] is a shadow.
|
||||
if it is neither, we can safely remove it.
|
||||
|
||||
XXX: This causes the previously mentioned fuckup.
|
||||
}
|
||||
for y := 0 to bmp.Height - 2 do
|
||||
for x := 0 to bmp.Width - 2 do
|
||||
begin
|
||||
@ -326,11 +367,20 @@ begin
|
||||
continue;
|
||||
if bmp.fastgetpixel(x,y) = clBlack then
|
||||
continue;
|
||||
if (bmp.fastgetpixel(x,y) <> bmp.fastgetpixel(x+1,y+1)) and (bmp.fastgetpixel(x+1,y+1) <> clpurple) then
|
||||
if (bmp.fastgetpixel(x,y) <> bmp.fastgetpixel(x+1,y+1)) and
|
||||
(bmp.fastgetpixel(x+1,y+1) <> clpurple) then
|
||||
bmp.fastsetpixel(x,y,{clAqua}0);
|
||||
end;
|
||||
|
||||
// Remove false shadow
|
||||
{
|
||||
Remove false shadow. (A shadow isn't larger than 1 pixel. So if x, y is
|
||||
shadow, thex x+1, y+1 can't be shadow. If it is, we can safely remove it.
|
||||
(As well as x+2, y+2, etc).
|
||||
|
||||
The tricky part of the algorithm is that it starts at the bottom,
|
||||
removing shadow point x,y if x-1,y-1 is also shadow.
|
||||
}
|
||||
|
||||
for y := bmp.Height - 1 downto 1 do
|
||||
for x := bmp.Width - 1 downto 1 do
|
||||
begin
|
||||
@ -346,24 +396,24 @@ begin
|
||||
end;
|
||||
|
||||
// Now we do another filter, with uptext chars identity 1 and 2.
|
||||
for y := bmp.Height - 2 downto 0 do
|
||||
for x := bmp.Width - 2 downto 0 do
|
||||
begin
|
||||
if bmp.fastgetpixel(x,y) = clPurple then
|
||||
continue;
|
||||
if bmp.fastgetpixel(x,y) = clBlack then
|
||||
continue;
|
||||
for y := bmp.Height - 2 downto 0 do
|
||||
for x := bmp.Width - 2 downto 0 do
|
||||
begin
|
||||
if bmp.fastgetpixel(x,y) = clPurple then
|
||||
continue;
|
||||
if bmp.fastgetpixel(x,y) = clBlack then
|
||||
continue;
|
||||
|
||||
// identity 1
|
||||
if (bmp.fastgetpixel(x,y) = bmp.fastgetpixel(x+1,y+1) ) then
|
||||
continue;
|
||||
// identity 1
|
||||
if (bmp.fastgetpixel(x,y) = bmp.fastgetpixel(x+1,y+1) ) then
|
||||
continue;
|
||||
|
||||
// identity 2
|
||||
if bmp.fastgetpixel(x+1,y+1) <> clPurple then
|
||||
begin
|
||||
bmp.fastsetpixel(x,y,clOlive);
|
||||
continue;
|
||||
end;
|
||||
// identity 2
|
||||
if bmp.fastgetpixel(x+1,y+1) <> clPurple then
|
||||
begin
|
||||
bmp.fastsetpixel(x,y,clOlive);
|
||||
continue;
|
||||
end;
|
||||
|
||||
// If we make it to here, it means the pixel is part of the uptext.
|
||||
end;
|
||||
@ -505,7 +555,7 @@ begin
|
||||
{$ENDIF}
|
||||
|
||||
// Filter 1
|
||||
FilterUpTextByColour(bmp,w,h);
|
||||
FilterUpTextByColour(bmp);
|
||||
{$IFDEF OCRSAVEBITMAP}
|
||||
bmp.SaveToFile(OCRDebugPath + 'ocrcol.bmp');
|
||||
{$ENDIF}
|
||||
@ -542,8 +592,8 @@ begin
|
||||
|
||||
// TODO:
|
||||
// We should make a different TPA
|
||||
// for each colour, rather than put them all in one. Noise can be a of a
|
||||
// differnet colour.
|
||||
// for each colour, rather than put them all in one. Noise can be of a
|
||||
// different colour.
|
||||
setlength(chars, charsbmp.height * charsbmp.width);
|
||||
charscount:=0;
|
||||
for y := 0 to charsbmp.height - 1 do
|
||||
|