├── .gitignore
├── BoxFileEditor.sln
├── BoxFileEditor
├── App.xaml
├── App.xaml.cs
├── BitmapHelper.cs
├── BlobDetector.cs
├── BoxFileEditor.csproj
├── Controls
│ ├── BitmapControl.cs
│ ├── TessBoxControl.cs
│ ├── TessBoxPanel.cs
│ └── TessImageView.cs
├── Images
│ ├── Create.png
│ ├── Delete.png
│ ├── DeleteHS.png
│ ├── Link.png
│ ├── OpenHS.png
│ └── SaveHS.png
├── MainViewModel.cs
├── MainWindow.xaml
├── MainWindow.xaml.cs
├── Properties
│ ├── AssemblyInfo.cs
│ ├── Resources.Designer.cs
│ ├── Resources.resx
│ ├── Settings.Designer.cs
│ └── Settings.settings
├── SelectedBoxWnd.xaml
├── SelectedBoxWnd.xaml.cs
├── Themes
│ └── Generic.xaml
├── TiffHelper.cs
└── UIHelper.cs
├── README.md
└── SharedBin
└── WPFToolkit.Extended.dll
/.gitignore:
--------------------------------------------------------------------------------
1 | #################
2 | ## Eclipse
3 | #################
4 |
5 | *.pydevproject
6 | .project
7 | .metadata
8 | bin/
9 | tmp/
10 | *.tmp
11 | *.bak
12 | *.swp
13 | *~.nib
14 | local.properties
15 | .classpath
16 | .settings/
17 | .loadpath
18 |
19 | # External tool builders
20 | .externalToolBuilders/
21 |
22 | # Locally stored "Eclipse launch configurations"
23 | *.launch
24 |
25 | # CDT-specific
26 | .cproject
27 |
28 | # PDT-specific
29 | .buildpath
30 |
31 |
32 | #################
33 | ## Visual Studio
34 | #################
35 |
36 | ## Ignore Visual Studio temporary files, build results, and
37 | ## files generated by popular Visual Studio add-ons.
38 |
39 | # User-specific files
40 | *.suo
41 | *.user
42 | *.sln.docstates
43 |
44 | # Build results
45 |
46 | [Dd]ebug/
47 | [Rr]elease/
48 | x64/
49 | build/
50 | [Bb]in/
51 | [Oo]bj/
52 |
53 | # MSTest test Results
54 | [Tt]est[Rr]esult*/
55 | [Bb]uild[Ll]og.*
56 |
57 | *_i.c
58 | *_p.c
59 | *.ilk
60 | *.meta
61 | *.obj
62 | *.pch
63 | *.pdb
64 | *.pgc
65 | *.pgd
66 | *.rsp
67 | *.sbr
68 | *.tlb
69 | *.tli
70 | *.tlh
71 | *.tmp
72 | *.tmp_proj
73 | *.log
74 | *.vspscc
75 | *.vssscc
76 | .builds
77 | *.pidb
78 | *.log
79 | *.scc
80 |
81 | # Visual C++ cache files
82 | ipch/
83 | *.aps
84 | *.ncb
85 | *.opensdf
86 | *.sdf
87 | *.cachefile
88 |
89 | # Visual Studio profiler
90 | *.psess
91 | *.vsp
92 | *.vspx
93 |
94 | # Guidance Automation Toolkit
95 | *.gpState
96 |
97 | # ReSharper is a .NET coding add-in
98 | _ReSharper*/
99 | *.[Rr]e[Ss]harper
100 |
101 | # TeamCity is a build add-in
102 | _TeamCity*
103 |
104 | # DotCover is a Code Coverage Tool
105 | *.dotCover
106 |
107 | # NCrunch
108 | *.ncrunch*
109 | .*crunch*.local.xml
110 |
111 | # Installshield output folder
112 | [Ee]xpress/
113 |
114 | # DocProject is a documentation generator add-in
115 | DocProject/buildhelp/
116 | DocProject/Help/*.HxT
117 | DocProject/Help/*.HxC
118 | DocProject/Help/*.hhc
119 | DocProject/Help/*.hhk
120 | DocProject/Help/*.hhp
121 | DocProject/Help/Html2
122 | DocProject/Help/html
123 |
124 | # Click-Once directory
125 | publish/
126 |
127 | # Publish Web Output
128 | *.Publish.xml
129 | *.pubxml
130 | *.publishproj
131 |
132 | # NuGet Packages Directory
133 | ## TODO: If you have NuGet Package Restore enabled, uncomment the next line
134 | #packages/
135 |
136 | # Windows Azure Build Output
137 | csx
138 | *.build.csdef
139 |
140 | # Windows Store app package directory
141 | AppPackages/
142 |
143 | # Others
144 | sql/
145 | *.Cache
146 | ClientBin/
147 | [Ss]tyle[Cc]op.*
148 | ~$*
149 | *~
150 | *.dbmdl
151 | *.[Pp]ublish.xml
152 | *.pfx
153 | *.publishsettings
154 |
155 | # RIA/Silverlight projects
156 | Generated_Code/
157 |
158 | # Backup & report files from converting an old project file to a newer
159 | # Visual Studio version. Backup files are not needed, because we have git ;-)
160 | _UpgradeReport_Files/
161 | Backup*/
162 | UpgradeLog*.XML
163 | UpgradeLog*.htm
164 |
165 | # SQL Server files
166 | App_Data/*.mdf
167 | App_Data/*.ldf
168 |
169 | #############
170 | ## Windows detritus
171 | #############
172 |
173 | # Windows image file caches
174 | Thumbs.db
175 | ehthumbs.db
176 |
177 | # Folder config file
178 | Desktop.ini
179 |
180 | # Recycle Bin used on file shares
181 | $RECYCLE.BIN/
182 |
183 | # Mac crap
184 | .DS_Store
185 |
186 |
187 | #############
188 | ## Python
189 | #############
190 |
191 | *.py[cod]
192 |
193 | # Packages
194 | *.egg
195 | *.egg-info
196 | dist/
197 | build/
198 | eggs/
199 | parts/
200 | var/
201 | sdist/
202 | develop-eggs/
203 | .installed.cfg
204 |
205 | # Installer logs
206 | pip-log.txt
207 |
208 | # Unit test / coverage reports
209 | .coverage
210 | .tox
211 |
212 | #Translations
213 | *.mo
214 |
215 | #Mr Developer
216 | .mr.developer.cfg
217 |
--------------------------------------------------------------------------------
/BoxFileEditor.sln:
--------------------------------------------------------------------------------
1 |
2 | Microsoft Visual Studio Solution File, Format Version 12.00
3 | # Visual Studio 2012
4 | Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "BoxFileEditor", "BoxFileEditor\BoxFileEditor.csproj", "{01BD3F62-B004-4422-A620-21F787C52AEE}"
5 | EndProject
6 | Global
7 | GlobalSection(SolutionConfigurationPlatforms) = preSolution
8 | Debug|Any CPU = Debug|Any CPU
9 | Release|Any CPU = Release|Any CPU
10 | EndGlobalSection
11 | GlobalSection(ProjectConfigurationPlatforms) = postSolution
12 | {01BD3F62-B004-4422-A620-21F787C52AEE}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
13 | {01BD3F62-B004-4422-A620-21F787C52AEE}.Debug|Any CPU.Build.0 = Debug|Any CPU
14 | {01BD3F62-B004-4422-A620-21F787C52AEE}.Release|Any CPU.ActiveCfg = Release|Any CPU
15 | {01BD3F62-B004-4422-A620-21F787C52AEE}.Release|Any CPU.Build.0 = Release|Any CPU
16 | EndGlobalSection
17 | GlobalSection(SolutionProperties) = preSolution
18 | HideSolutionNode = FALSE
19 | EndGlobalSection
20 | EndGlobal
21 |
--------------------------------------------------------------------------------
/BoxFileEditor/App.xaml:
--------------------------------------------------------------------------------
1 |
5 |
6 |
7 |
8 |
9 |
--------------------------------------------------------------------------------
/BoxFileEditor/App.xaml.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.Collections.Generic;
3 | using System.Configuration;
4 | using System.Diagnostics;
5 | using System.Drawing;
6 | using System.Linq;
7 | using System.Windows;
8 |
9 | namespace BoxFileEditor
10 | {
11 | ///
12 | /// Interaction logic for App.xaml
13 | ///
14 | public partial class App : Application
15 | {
16 | private void App_OnStartup(object sender, StartupEventArgs e)
17 | {
18 | //using (var img = (System.Drawing.Bitmap)Image.FromFile(@"D:\ScottDev\TesseractOcr\Training\ocr.e13b.exp0.tif"))
19 | //{
20 | // var blobDetector = new BlobDetector();
21 | // var blobs = blobDetector.LocateBlobs(img);
22 | // Debug.WriteLine("{0} blobs found!", blobs.Length);
23 | //}
24 | }
25 |
26 | private void App_OnExit(object sender, ExitEventArgs e)
27 | {
28 |
29 | }
30 | }
31 | }
32 |
--------------------------------------------------------------------------------
/BoxFileEditor/BitmapHelper.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.Collections.Generic;
3 | using System.Linq;
4 | using System.Text;
5 |
6 | namespace BoxFileEditor
7 | {
8 | public static class BitmapHelper
9 | {
10 | public static int GetBitmapDataStride(int bitsPerPixel, int pixelWidth)
11 | {
12 | int bytesPerRow = 0;
13 | //raw bytes per row required
14 | if (bitsPerPixel == 1)
15 | bytesPerRow = (pixelWidth + 7) / 8;
16 | else if (bitsPerPixel == 8)
17 | bytesPerRow = pixelWidth;
18 | else if (bitsPerPixel == 32)
19 | bytesPerRow = pixelWidth * 4;
20 | //stride must be aligned on 4 byte boundary...
21 | int stride = ((bytesPerRow + 3) & ~3);
22 | return stride;
23 | }
24 |
25 | public static int GetBitmapDataByteCount(int bitsPerPixel, int pixelWidth, int pixelHeight)
26 | {
27 | return pixelHeight * GetBitmapDataStride(bitsPerPixel, pixelWidth);
28 | }
29 |
30 | }
31 | }
32 |
--------------------------------------------------------------------------------
/BoxFileEditor/BlobDetector.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.Collections.Generic;
3 | using System.Diagnostics;
4 | using System.Drawing;
5 | using System.Drawing.Imaging;
6 | using System.Linq;
7 | using System.Text;
8 |
9 | namespace BoxFileEditor
10 | {
11 | public class BlobDetector
12 | {
13 | public int MinWidth { get; set; }
14 | public int MaxWidth { get; set; }
15 |
16 | public int MinHeight { get; set; }
17 | public int MaxHeight { get; set; }
18 |
19 | public int MinMass { get; set; }
20 | public int MaxMass { get; set; }
21 |
22 | public BlobDetector()
23 | {
24 | MinWidth = 2;
25 | MaxWidth = 50;
26 |
27 | MinHeight = 5;
28 | MaxHeight = 50;
29 |
30 | MinMass = 10;
31 | MaxMass = 300;
32 | }
33 |
34 | unsafe byte GetPixelColor(byte* pBits, int stride, int x, int y)
35 | {
36 | return *(pBits + (y * stride) + x);
37 | }
38 |
39 | unsafe void SetPixelColor(byte* pBits, int stride, int x, int y, byte newColor)
40 | {
41 | *(pBits + (y * stride) + x) = newColor;
42 | }
43 |
44 | unsafe private Rectangle GetBoundsRect(BitmapData inputData, Rectangle area, byte color)
45 | {
46 | byte* pBits = (byte*)inputData.Scan0.ToPointer();
47 | int stride = inputData.Stride;
48 |
49 | if (area == Rectangle.Empty)
50 | area = new Rectangle(0, 0, inputData.Width, inputData.Height);
51 |
52 | int top = -1;
53 | int bottom = -1;
54 | int left = -1;
55 | int right = -1;
56 |
57 | int leftMax = -1;
58 |
59 | for (int y = area.Y; y < area.Bottom; y++)
60 | {
61 | for (int x = area.X; x < area.Right; x++)
62 | {
63 | if (GetPixelColor(pBits, stride, x, y) == color)
64 | {
65 | if (leftMax == -1 || x < leftMax)
66 | leftMax = x;
67 | top = y;
68 | break;
69 | }
70 | }
71 | if (top != -1)
72 | break;
73 | }
74 |
75 | if (top != -1)
76 | {
77 | //search for the bottom, if we don't find a bottom
78 | //point, it will be the same as the top
79 | for (int y = area.Bottom - 1; y >= top; y--)
80 | {
81 | for (int x = area.X; x < area.Right; x++)
82 | {
83 | if (GetPixelColor(pBits, stride, x, y) == color)
84 | {
85 | if (leftMax == -1 || x < leftMax)
86 | leftMax = x;
87 | bottom = y;
88 | break;
89 | }
90 | }
91 | if (bottom != -1)
92 | break;
93 | }
94 | }
95 |
96 | //if we didn't find a top & bottom, just leave there is no
97 | //pixel of the specified color.
98 | if (top == -1 && bottom == -1)
99 | return Rectangle.Empty;
100 |
101 | for (int x = area.X; x <= leftMax; x++)
102 | {
103 | for (int y = top; y <= bottom; y++)
104 | {
105 | if (GetPixelColor(pBits, stride, x, y) == color)
106 | {
107 | left = x;
108 | break;
109 | }
110 | }
111 | if (left != -1)
112 | break;
113 | }
114 |
115 | if (left != -1)
116 | {
117 | for (int x = area.Right - 1; x >= leftMax; x--)
118 | {
119 | for (int y = top; y <= bottom; y++)
120 | {
121 | if (GetPixelColor(pBits, stride, x, y) == color)
122 | {
123 | right = x;
124 | break;
125 | }
126 | }
127 | if (right != -1)
128 | break;
129 | }
130 | }
131 |
132 | //if we didn't find a left & right, just leave there is no
133 | //pixel of the specified color.
134 | if (left == -1 && right == -1)
135 | return Rectangle.Empty;
136 |
137 | return new Rectangle(left, top, (right - left) + 1, (bottom - top) + 1);
138 | }
139 |
140 | private void GrowRectangle(ref Rectangle r, int x, int y)
141 | {
142 | if (x < r.X)
143 | {
144 | r.Width += (r.X - x);
145 | r.X = x;
146 | }
147 | if (y < r.Y)
148 | {
149 | r.Height += (r.Y - y);
150 | r.Y = y;
151 | }
152 | if (x > r.Right - 1)
153 | {
154 | r.Width += (x - (r.Right - 1));
155 | }
156 | if (y > r.Bottom - 1)
157 | {
158 | r.Height += (y - (r.Bottom - 1));
159 | }
160 | }
161 |
162 | unsafe BlobInfo FillBlob(BitmapData inputData, Point pt, byte newColor)
163 | {
164 | byte* pBits = (byte*)inputData.Scan0.ToPointer();
165 | int stride = inputData.Stride;
166 | byte oldColor = GetPixelColor(pBits, stride, pt.X, pt.Y);
167 | var info = new BlobInfo();
168 | if (oldColor == newColor)
169 | return info;
170 |
171 | var s = new Stack(32);
172 | int h = inputData.Height;
173 | int w = inputData.Width;
174 | int y1 = 0;
175 | bool spanLeft = false;
176 | bool spanRight = false;
177 |
178 | info.Bounds = new Rectangle(pt, new Size(1, 1));
179 | s.Push(pt);
180 |
181 | long xCount = 0;
182 | long yCount = 0;
183 |
184 | while (s.Count != 0)
185 | {
186 | pt = s.Pop();
187 | y1 = pt.Y;
188 | while (y1 >= 0 && GetPixelColor(pBits, stride, pt.X, y1) == oldColor)
189 | y1--;
190 |
191 | y1++;
192 | spanLeft = spanRight = false;
193 | while (y1 < h && GetPixelColor(pBits, stride, pt.X, y1) == oldColor)
194 | {
195 | SetPixelColor(pBits, stride, pt.X, y1, newColor);
196 | xCount += pt.X;
197 | yCount += y1;
198 | //keep track of info...
199 | GrowRectangle(ref info._bounds, pt.X, y1);
200 | info.Mass++;
201 |
202 | if (!spanLeft && pt.X > 0 && GetPixelColor(pBits, stride, pt.X - 1, y1) == oldColor)
203 | {
204 | s.Push(new Point(pt.X - 1, y1));
205 | spanLeft = true;
206 | }
207 | else if (spanLeft && pt.X > 0 && GetPixelColor(pBits, stride, pt.X - 1, y1) != oldColor)
208 | {
209 | spanLeft = false;
210 | }
211 | if (!spanRight && pt.X < w - 1 && GetPixelColor(pBits, stride, pt.X + 1, y1) == oldColor)
212 | {
213 | s.Push(new Point(pt.X + 1, y1));
214 | spanRight = true;
215 | }
216 | else if (spanRight && pt.X < w - 1 && GetPixelColor(pBits, stride, pt.X + 1, y1) != oldColor)
217 | {
218 | spanRight = false;
219 | }
220 | y1++;
221 | }
222 | }
223 |
224 | info.CenterOfMass = new PointF((float)xCount/info.Mass, (float)yCount/info.Mass);
225 |
226 | return info;
227 | }
228 |
229 | public BlobInfo[] LocateBlobs(Bitmap image)
230 | {
231 | return LocateBlobs(image, new Rectangle(0, 0, image.Width, image.Height));
232 | }
233 |
234 | unsafe public BlobInfo[] LocateBlobs(Bitmap image, Rectangle area)
235 | {
236 | const byte clrSymbol = 0;
237 | const byte clrCompleted = 50;
238 |
239 | var working = (Bitmap)image.Clone();
240 | var inputData = working.LockBits(area, ImageLockMode.ReadOnly, PixelFormat.Format8bppIndexed);
241 |
242 | try
243 | {
244 | var blobs = new List();
245 | var bits = (byte*)inputData.Scan0.ToPointer();
246 | int stride = inputData.Stride;
247 |
248 | var rLast = Rectangle.Empty;
249 | while (true)
250 | {
251 | var rMain = GetBoundsRect(inputData, rLast, clrSymbol);
252 | if (rMain.IsEmpty)
253 | break;
254 |
255 | rLast = rMain;
256 |
257 | int x = rMain.X;
258 | for (int y = rMain.Y; y < rMain.Bottom; y++)
259 | {
260 | if (GetPixelColor(bits, stride, x, y) == clrSymbol)
261 | {
262 | //fill blob to mark it.
263 | var blob = FillBlob(inputData, new Point(x, y), clrCompleted);
264 | //get the bounds of the marked blob...
265 | if (blob.Bounds.Width >= MinWidth && blob.Bounds.Height >= MinHeight
266 | && blob.Bounds.Width <= MaxWidth && blob.Bounds.Height <= MaxHeight)
267 | {
268 | blob.Bounds.Offset(area.Location);
269 | blob.Bounds.Inflate(2, 2);
270 | blobs.Add(blob);
271 | }
272 |
273 | }
274 |
275 | }
276 |
277 | }
278 | //return the list in top to bottom, left to right order
279 | return blobs.OrderBy(b => b.Bounds.Y).ThenBy(b => b.Bounds.X).ToArray();
280 | }
281 | finally
282 | {
283 | if(inputData != null)
284 | working.UnlockBits(inputData);
285 | }
286 |
287 | }
288 |
289 | }
290 |
291 | public class BlobInfo
292 | {
293 | protected internal Rectangle _bounds;
294 | public Rectangle Bounds
295 | {
296 | get { return _bounds; }
297 | set { _bounds = value; }
298 | }
299 |
300 | public int Mass { get; protected internal set; }
301 |
302 | public float Fullness
303 | {
304 | get { return (float)Mass / (float)(_bounds.Width * _bounds.Height); }
305 | }
306 |
307 | public Point Center
308 | {
309 | get { return new Point(_bounds.X + (_bounds.Width/2), _bounds.Y + (_bounds.Height/2)); }
310 | }
311 |
312 | public PointF CenterOfMass { get; protected internal set; }
313 | }
314 | }
315 |
--------------------------------------------------------------------------------
/BoxFileEditor/BoxFileEditor.csproj:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | Debug
6 | AnyCPU
7 | {01BD3F62-B004-4422-A620-21F787C52AEE}
8 | WinExe
9 | Properties
10 | BoxFileEditor
11 | BoxFileEditor
12 | v4.0
13 | 512
14 | {60dc8134-eba5-43b8-bcc9-bb4bc16c2548};{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}
15 | 4
16 |
17 |
18 | AnyCPU
19 | true
20 | full
21 | false
22 | ..\_Debug\
23 | DEBUG;TRACE
24 | prompt
25 | 4
26 | true
27 |
28 |
29 | AnyCPU
30 | pdbonly
31 | true
32 | ..\_Release\
33 | TRACE
34 | prompt
35 | 4
36 | true
37 |
38 |
39 |
40 |
41 |
42 |
43 |
44 | 4.0
45 |
46 |
47 |
48 |
49 |
50 | ..\SharedBin\WPFToolkit.Extended.dll
51 |
52 |
53 |
54 |
55 | MSBuild:Compile
56 | Designer
57 |
58 |
59 |
60 |
61 |
62 |
63 |
64 | SelectedBoxWnd.xaml
65 |
66 |
67 |
68 |
69 | MSBuild:Compile
70 | Designer
71 |
72 |
73 | App.xaml
74 | Code
75 |
76 |
77 |
78 |
79 | MainWindow.xaml
80 | Code
81 |
82 |
83 | Designer
84 | MSBuild:Compile
85 |
86 |
87 | Designer
88 | MSBuild:Compile
89 |
90 |
91 |
92 |
93 | Code
94 |
95 |
96 | True
97 | True
98 | Resources.resx
99 |
100 |
101 | True
102 | Settings.settings
103 | True
104 |
105 |
106 | ResXFileCodeGenerator
107 | Resources.Designer.cs
108 |
109 |
110 | SettingsSingleFileGenerator
111 | Settings.Designer.cs
112 |
113 |
114 |
115 |
116 |
117 |
118 |
119 |
120 |
121 |
122 |
123 |
124 |
125 |
126 |
127 |
134 |
--------------------------------------------------------------------------------
/BoxFileEditor/Controls/BitmapControl.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.Collections.Generic;
3 | using System.Linq;
4 | using System.Text;
5 | using System.Windows;
6 | using System.Windows.Media;
7 | using System.Windows.Media.Imaging;
8 |
9 | namespace BoxFileEditor
10 | {
11 | ///
12 | /// The Bitmap element is using the ActualWidth/Height of the image for the control size.
13 | /// It also offsets the image to avoid blurring over pixel boundaries. (todo: Can this also be achieved with SnapToDevicePixels?)
14 | /// http://blogs.msdn.com/b/dwayneneed/archive/2007/10/05/blurry-bitmaps.aspx
15 | ///
16 | public class BitmapControl : UIElement
17 | {
18 | public static readonly DependencyProperty SourceProperty = DependencyProperty.Register(
19 | "Source",
20 | typeof(BitmapSource),
21 | typeof(BitmapControl),
22 | new FrameworkPropertyMetadata(
23 | null,
24 | FrameworkPropertyMetadataOptions.AffectsRender |
25 | FrameworkPropertyMetadataOptions.AffectsMeasure,
26 | OnSourceChanged));
27 |
28 | private readonly EventHandler _sourceDownloaded;
29 | private readonly EventHandler _sourceFailed;
30 | private Point _pixelOffset;
31 |
32 | public BitmapControl()
33 | {
34 | _sourceDownloaded = new EventHandler(OnSourceDownloaded);
35 | _sourceFailed = new EventHandler(OnSourceFailed);
36 |
37 | LayoutUpdated += OnLayoutUpdated;
38 | }
39 |
40 | public BitmapSource Source
41 | {
42 | get { return (BitmapSource)GetValue(SourceProperty); }
43 | set { SetValue(SourceProperty, value); }
44 | }
45 |
46 | public event EventHandler BitmapFailed;
47 |
48 | // Return our measure size to be the size needed to display the bitmap pixels.
49 | protected override Size MeasureCore(Size availableSize)
50 | {
51 | var measureSize = new Size();
52 |
53 | BitmapSource bitmapSource = Source;
54 | if (bitmapSource != null)
55 | {
56 | PresentationSource ps = PresentationSource.FromVisual(this);
57 | if (ps != null)
58 | {
59 | Matrix fromDevice = ps.CompositionTarget.TransformFromDevice;
60 |
61 | var pixelSize = new Vector(bitmapSource.PixelWidth, bitmapSource.PixelHeight);
62 | Vector measureSizeV = fromDevice.Transform(pixelSize);
63 | measureSize = new Size(measureSizeV.X, measureSizeV.Y);
64 | }
65 | }
66 |
67 | return measureSize;
68 | }
69 |
70 | protected override void OnRender(DrawingContext dc)
71 | {
72 | BitmapSource bitmapSource = Source;
73 | if (bitmapSource != null)
74 | {
75 | _pixelOffset = GetPixelOffset();
76 |
77 | // Render the bitmap offset by the needed amount to align to pixels.
78 | dc.DrawImage(bitmapSource, new Rect(_pixelOffset, DesiredSize));
79 | }
80 | }
81 |
82 | private static void OnSourceChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
83 | {
84 | var bmpCtl = (BitmapControl)d;
85 |
86 | var oldValue = (BitmapSource)e.OldValue;
87 | var newValue = (BitmapSource)e.NewValue;
88 |
89 | if (((oldValue != null) && (bmpCtl._sourceDownloaded != null)) &&
90 | (!oldValue.IsFrozen && (oldValue is BitmapSource)))
91 | {
92 | (oldValue).DownloadCompleted -= bmpCtl._sourceDownloaded;
93 | (oldValue).DownloadFailed -= bmpCtl._sourceFailed;
94 | // ((BitmapSource)newValue).DecodeFailed -= bitmap._sourceFailed; // 3.5
95 | }
96 | if (((newValue != null) && (newValue is BitmapSource)) && !newValue.IsFrozen)
97 | {
98 | (newValue).DownloadCompleted += bmpCtl._sourceDownloaded;
99 | (newValue).DownloadFailed += bmpCtl._sourceFailed;
100 | // ((BitmapSource)newValue).DecodeFailed += bitmap._sourceFailed; // 3.5
101 | }
102 | }
103 |
104 | private void OnSourceDownloaded(object sender, EventArgs e)
105 | {
106 | InvalidateMeasure();
107 | InvalidateVisual();
108 | }
109 |
110 | private void OnSourceFailed(object sender, ExceptionEventArgs e)
111 | {
112 | Source = null; // setting a local value seems scetchy...
113 |
114 | BitmapFailed(this, e);
115 | }
116 |
117 | private void OnLayoutUpdated(object sender, EventArgs e)
118 | {
119 | // This event just means that layout happened somewhere. However, this is
120 | // what we need since layout anywhere could affect our pixel positioning.
121 | Point pixelOffset = GetPixelOffset();
122 | if (!AreClose(pixelOffset, _pixelOffset))
123 | {
124 | InvalidateVisual();
125 | }
126 | }
127 |
128 | // Gets the matrix that will convert a point from "above" the
129 | // coordinate space of a visual into the the coordinate space
130 | // "below" the visual.
131 | private Matrix GetVisualTransform(Visual v)
132 | {
133 | if (v != null)
134 | {
135 | Matrix m = Matrix.Identity;
136 |
137 | Transform transform = VisualTreeHelper.GetTransform(v);
138 | if (transform != null)
139 | {
140 | Matrix cm = transform.Value;
141 | m = Matrix.Multiply(m, cm);
142 | }
143 |
144 | Vector offset = VisualTreeHelper.GetOffset(v);
145 | m.Translate(offset.X, offset.Y);
146 |
147 | return m;
148 | }
149 |
150 | return Matrix.Identity;
151 | }
152 |
153 | private Point TryApplyVisualTransform(Point point, Visual v, bool inverse, bool throwOnError, out bool success)
154 | {
155 | success = true;
156 | if (v != null)
157 | {
158 | Matrix visualTransform = GetVisualTransform(v);
159 | if (inverse)
160 | {
161 | if (!throwOnError && !visualTransform.HasInverse)
162 | {
163 | success = false;
164 | return new Point(0, 0);
165 | }
166 | visualTransform.Invert();
167 | }
168 | point = visualTransform.Transform(point);
169 | }
170 | return point;
171 | }
172 |
173 | private Point ApplyVisualTransform(Point point, Visual v, bool inverse)
174 | {
175 | bool success = true;
176 | return TryApplyVisualTransform(point, v, inverse, true, out success);
177 | }
178 |
179 | private Point GetPixelOffset()
180 | {
181 | var pixelOffset = new Point();
182 |
183 | PresentationSource ps = PresentationSource.FromVisual(this);
184 | if (ps != null)
185 | {
186 | Visual rootVisual = ps.RootVisual;
187 |
188 | // Transform (0,0) from this element up to pixels.
189 | pixelOffset = TransformToAncestor(rootVisual).Transform(pixelOffset);
190 | pixelOffset = ApplyVisualTransform(pixelOffset, rootVisual, false);
191 | pixelOffset = ps.CompositionTarget.TransformToDevice.Transform(pixelOffset);
192 |
193 | // Round the origin to the nearest whole pixel.
194 | pixelOffset.X = Math.Round(pixelOffset.X);
195 | pixelOffset.Y = Math.Round(pixelOffset.Y);
196 |
197 | // Transform the whole-pixel back to this element.
198 | pixelOffset = ps.CompositionTarget.TransformFromDevice.Transform(pixelOffset);
199 | pixelOffset = ApplyVisualTransform(pixelOffset, rootVisual, true);
200 | pixelOffset = rootVisual.TransformToDescendant(this).Transform(pixelOffset);
201 | }
202 |
203 | return pixelOffset;
204 | }
205 |
206 | private bool AreClose(Point point1, Point point2)
207 | {
208 | return AreClose(point1.X, point2.X) && AreClose(point1.Y, point2.Y);
209 | }
210 |
211 | private bool AreClose(double value1, double value2)
212 | {
213 | if (value1 == value2)
214 | {
215 | return true;
216 | }
217 | double delta = value1 - value2;
218 | return ((delta < 1.53E-06) && (delta > -1.53E-06));
219 | }
220 | }
221 | }
222 |
--------------------------------------------------------------------------------
/BoxFileEditor/Controls/TessBoxControl.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.Collections.Generic;
3 | using System.ComponentModel;
4 | using System.Linq;
5 | using System.Text;
6 | using System.Windows;
7 | using System.Windows.Controls;
8 | using System.Windows.Controls.Primitives;
9 | using System.Windows.Data;
10 | using System.Windows.Documents;
11 | using System.Windows.Input;
12 | using System.Windows.Media;
13 | using System.Windows.Media.Imaging;
14 | using System.Windows.Navigation;
15 | using System.Windows.Shapes;
16 |
17 | namespace BoxFileEditor
18 | {
19 | ///
20 | /// Follow steps 1a or 1b and then 2 to use this custom control in a XAML file.
21 | ///
22 | /// Step 1a) Using this custom control in a XAML file that exists in the current project.
23 | /// Add this XmlNamespace attribute to the root element of the markup file where it is
24 | /// to be used:
25 | ///
26 | /// xmlns:MyNamespace="clr-namespace:BoxFileEditor"
27 | ///
28 | ///
29 | /// Step 1b) Using this custom control in a XAML file that exists in a different project.
30 | /// Add this XmlNamespace attribute to the root element of the markup file where it is
31 | /// to be used:
32 | ///
33 | /// xmlns:MyNamespace="clr-namespace:BoxFileEditor;assembly=BoxFileEditor"
34 | ///
35 | /// You will also need to add a project reference from the project where the XAML file lives
36 | /// to this project and Rebuild to avoid compilation errors:
37 | ///
38 | /// Right click on the target project in the Solution Explorer and
39 | /// "Add Reference"->"Projects"->[Browse to and select this project]
40 | ///
41 | ///
42 | /// Step 2)
43 | /// Go ahead and use your control in the XAML file.
44 | ///
45 | ///
46 | ///
47 | ///
48 | public class TessBoxControl : Control, INotifyPropertyChanged
49 | {
50 | public static readonly DependencyProperty IsSelectedProperty =
51 | DependencyProperty.Register("IsSelected", typeof (bool), typeof (TessBoxControl), new PropertyMetadata(default(bool), (d, a) => ((TessBoxControl)d).OnIsSelectedChanged((bool)a.NewValue)));
52 |
53 | private static void PropertyChangedCallback(DependencyObject dependencyObject, DependencyPropertyChangedEventArgs dependencyPropertyChangedEventArgs)
54 | {
55 | throw new NotImplementedException();
56 | }
57 |
58 | public bool IsSelected
59 | {
60 | get { return (bool) GetValue(IsSelectedProperty); }
61 | set { SetValue(IsSelectedProperty, value); }
62 | }
63 |
64 | public static readonly DependencyProperty ValueProperty =
65 | DependencyProperty.Register("Value", typeof (string), typeof (TessBoxControl), new PropertyMetadata(default(string)));
66 |
67 | public string Value
68 | {
69 | get { return (string) GetValue(ValueProperty); }
70 | set { SetValue(ValueProperty, value); }
71 | }
72 |
73 | public double Left
74 | {
75 | get { return Canvas.GetLeft(this); }
76 | set
77 | {
78 | Canvas.SetLeft(this, value);
79 | NotifyPropertyChanged("Left");
80 | }
81 | }
82 |
83 | public double Top
84 | {
85 | get { return Canvas.GetTop(this); }
86 | set
87 | {
88 | Canvas.SetTop(this, value);
89 | NotifyPropertyChanged("Top");
90 | }
91 | }
92 |
93 | private Border _normalBorder = null;
94 | private Border _selBorder = null;
95 |
96 | static TessBoxControl()
97 | {
98 | DefaultStyleKeyProperty.OverrideMetadata(typeof(TessBoxControl), new FrameworkPropertyMetadata(typeof(TessBoxControl)));
99 | }
100 |
101 | public event PropertyChangedEventHandler PropertyChanged;
102 |
103 | public TessBoxControl()
104 | {
105 |
106 | }
107 |
108 | protected virtual void NotifyPropertyChanged(string propertyName)
109 | {
110 | var handler = PropertyChanged;
111 | if(handler != null)
112 | handler.Invoke(this, new PropertyChangedEventArgs(propertyName));
113 | }
114 |
115 | public override void OnApplyTemplate()
116 | {
117 | base.OnApplyTemplate();
118 |
119 | _normalBorder = GetTemplateChild("normalBorder") as Border;
120 | _selBorder = GetTemplateChild("selBorder") as Border;
121 |
122 | }
123 |
124 | protected override void OnRenderSizeChanged(SizeChangedInfo sizeInfo)
125 | {
126 | base.OnRenderSizeChanged(sizeInfo);
127 | NotifyPropertyChanged("RenderSize");
128 | }
129 |
130 | protected void OnIsSelectedChanged(bool isSelected)
131 | {
132 | if (isSelected)
133 | {
134 | _normalBorder.Opacity = 0;
135 | _selBorder.Opacity = 1;
136 | }
137 | else
138 | {
139 | _normalBorder.Opacity = 1;
140 | _selBorder.Opacity = 0;
141 | }
142 | }
143 |
144 | protected override HitTestResult HitTestCore(PointHitTestParameters hitTestParameters)
145 | {
146 | //return base.HitTestCore(hitTestParameters);
147 | return new PointHitTestResult(this, hitTestParameters.HitPoint);
148 | }
149 |
150 | }
151 | }
152 |
--------------------------------------------------------------------------------
/BoxFileEditor/Controls/TessBoxPanel.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.Collections.Generic;
3 | using System.Linq;
4 | using System.Text;
5 | using System.Windows;
6 | using System.Windows.Controls;
7 |
8 | namespace BoxFileEditor
9 | {
10 | public class TessBoxPanel : Panel
11 | {
12 | protected override Size MeasureOverride(Size availableSize)
13 | {
14 | foreach (UIElement child in Children)
15 | {
16 | child.Measure(availableSize);
17 | }
18 | return new Size();
19 | }
20 |
21 | protected override Size ArrangeOverride(Size finalSize)
22 | {
23 | foreach (UIElement child in Children)
24 | {
25 | var childRect = new Rect(0,0,10,10);
26 | child.Arrange(childRect);
27 | }
28 | return finalSize;
29 | }
30 | }
31 | }
32 |
--------------------------------------------------------------------------------
/BoxFileEditor/Controls/TessImageView.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.Collections.Generic;
3 | using System.Diagnostics;
4 | using System.Linq;
5 | using System.Text;
6 | using System.Windows;
7 | using System.Windows.Controls;
8 | using System.Windows.Controls.Primitives;
9 | using System.Windows.Data;
10 | using System.Windows.Documents;
11 | using System.Windows.Input;
12 | using System.Windows.Media;
13 | using System.Windows.Media.Imaging;
14 | using System.Windows.Navigation;
15 | using System.Windows.Shapes;
16 |
17 | namespace BoxFileEditor
18 | {
19 | ///
20 | /// Follow steps 1a or 1b and then 2 to use this custom control in a XAML file.
21 | ///
22 | /// Step 1a) Using this custom control in a XAML file that exists in the current project.
23 | /// Add this XmlNamespace attribute to the root element of the markup file where it is
24 | /// to be used:
25 | ///
26 | /// xmlns:MyNamespace="clr-namespace:BoxFileEditor.Controls"
27 | ///
28 | ///
29 | /// Step 1b) Using this custom control in a XAML file that exists in a different project.
30 | /// Add this XmlNamespace attribute to the root element of the markup file where it is
31 | /// to be used:
32 | ///
33 | /// xmlns:MyNamespace="clr-namespace:BoxFileEditor.Controls;assembly=BoxFileEditor.Controls"
34 | ///
35 | /// You will also need to add a project reference from the project where the XAML file lives
36 | /// to this project and Rebuild to avoid compilation errors:
37 | ///
38 | /// Right click on the target project in the Solution Explorer and
39 | /// "Add Reference"->"Projects"->[Browse to and select this project]
40 | ///
41 | ///
42 | /// Step 2)
43 | /// Go ahead and use your control in the XAML file.
44 | ///
45 | ///
46 | ///
47 | ///
48 | public class TessImageView : MultiSelector
49 | {
50 | public static readonly DependencyProperty ImageProperty =
51 | DependencyProperty.Register("Image", typeof (BitmapSource), typeof (TessImageView), new PropertyMetadata(default(BitmapSource)));
52 |
53 | public BitmapSource Image
54 | {
55 | get { return (BitmapSource) GetValue(ImageProperty); }
56 | set { SetValue(ImageProperty, value); }
57 | }
58 |
59 | private Image _backImage = null;
60 | private Grid _boxHost = null;
61 | private Canvas _rubberBandHost = null;
62 | private Rectangle _rubberBand = null;
63 |
64 | private TessBoxControl _mouseDownHitBox = null;
65 | private Point _mouseDownPos;
66 |
67 | public event EventHandler DeleteSelected;
68 | public event EventHandler MergeSelected;
69 | public event CreateBoxHandler CreateBox;
70 |
71 | static TessImageView()
72 | {
73 | DefaultStyleKeyProperty.OverrideMetadata(typeof(TessImageView), new FrameworkPropertyMetadata(typeof(TessImageView)));
74 | }
75 |
76 | public TessImageView()
77 | {
78 | }
79 |
80 | protected override void OnPreviewKeyDown(KeyEventArgs e)
81 | {
82 | var box = SelectedItem as TessBoxControl;
83 | if (e.Key == Key.Left)
84 | {
85 | if (box != null)
86 | {
87 | if (Keyboard.IsKeyDown(Key.LeftShift) || Keyboard.IsKeyDown(Key.RightShift))
88 | box.Width--;
89 | else
90 | box.Left--;
91 | }
92 | e.Handled = true;
93 | }
94 | else if (e.Key == Key.Right)
95 | {
96 | if (box != null)
97 | {
98 | if (Keyboard.IsKeyDown(Key.LeftShift) || Keyboard.IsKeyDown(Key.RightShift))
99 | box.Width++;
100 | else
101 | box.Left++;
102 | }
103 | e.Handled = true;
104 | }
105 | else if (e.Key == Key.Up)
106 | {
107 | if (box != null)
108 | {
109 | if (Keyboard.IsKeyDown(Key.LeftShift) || Keyboard.IsKeyDown(Key.RightShift))
110 | box.Height--;
111 | else
112 | box.Top--;
113 | }
114 | e.Handled = true;
115 | }
116 | else if (e.Key == Key.Down)
117 | {
118 | if (box != null)
119 | {
120 | if (Keyboard.IsKeyDown(Key.LeftShift) || Keyboard.IsKeyDown(Key.RightShift))
121 | box.Height++;
122 | else
123 | box.Top++;
124 | }
125 | e.Handled = true;
126 | }
127 | else if (e.Key == Key.Tab)
128 | {
129 | if (box != null)
130 | {
131 | var selContainer = ItemContainerGenerator.ContainerFromItem(box);
132 | int index = ItemContainerGenerator.IndexFromContainer(selContainer);
133 | if (Keyboard.IsKeyDown(Key.LeftShift) || Keyboard.IsKeyDown(Key.RightShift))
134 | {
135 | index--;
136 | if (index < 0) index = Items.Count-1;
137 | }
138 | else
139 | {
140 | index++;
141 | if (index >= Items.Count - 1) index = 0;
142 | }
143 | var nextContainer = ItemContainerGenerator.ContainerFromIndex(index);
144 | if (nextContainer != null)
145 | {
146 | var nextBox = ItemContainerGenerator.ItemFromContainer(nextContainer);
147 | SelectedItem = nextBox;
148 | }
149 | }
150 | e.Handled = true;
151 | }
152 | else if (e.Key == Key.Delete)
153 | {
154 | var handler = DeleteSelected;
155 | if(handler != null)
156 | handler.Invoke(this, new EventArgs());
157 | e.Handled = true;
158 | }
159 | else if (e.Key == Key.Insert)
160 | {
161 | var handler = MergeSelected;
162 | if(handler != null)
163 | handler.Invoke(this, new EventArgs());
164 | e.Handled = true;
165 | }
166 | base.OnPreviewKeyDown(e);
167 | }
168 |
169 | public void ScrollIntoView(object item)
170 | {
171 | var container = ItemContainerGenerator.ContainerFromItem(item) as TessBoxControl;
172 | if (container != null)
173 | {
174 | var targetRect = new Rect(new Point(container.Left, container.Top), new Size(container.Width, container.Height));
175 | targetRect.Inflate(100, 50);
176 | _boxHost.BringIntoView(targetRect);
177 | }
178 | }
179 |
180 | public override void OnApplyTemplate()
181 | {
182 | base.OnApplyTemplate();
183 |
184 |
185 | _boxHost = GetTemplateChild("boxHost") as Grid;
186 | _backImage = GetTemplateChild("backImage") as Image;
187 | _rubberBandHost = GetTemplateChild("rubberBandHost") as Canvas;
188 |
189 | _boxHost.MouseLeftButtonDown += _boxHost_MouseLeftButtonDown;
190 | _boxHost.MouseLeftButtonUp += _boxHost_MouseLeftButtonUp;
191 | _boxHost.MouseMove += _boxHost_MouseMove;
192 | }
193 |
194 | private IEnumerable GetHitBoxes(Rect rect)
195 | {
196 | var boxes = new List();
197 | for (int i = 0; i < Items.Count; i++)
198 | {
199 | var box = ItemContainerGenerator.ContainerFromIndex(i) as TessBoxControl;
200 | var boxRect = new Rect(box.Left, box.Top, box.Width, box.Height);
201 | if(rect.Contains(boxRect))
202 | boxes.Add(box);
203 | }
204 |
205 | return boxes;
206 | }
207 |
208 | private TessBoxControl GetHitBox(Point pt)
209 | {
210 | var result = VisualTreeHelper.HitTest(_boxHost, pt);
211 | if (result != null)
212 | {
213 | var hitControl = result.VisualHit;
214 | if (hitControl != null && !(hitControl is TessBoxControl))
215 | hitControl = UIHelper.GetParent(hitControl);
216 |
217 | if (hitControl != null && hitControl is TessBoxControl)
218 | return hitControl as TessBoxControl;
219 | }
220 | return null;
221 | }
222 |
223 | void _boxHost_MouseLeftButtonDown(object sender, MouseButtonEventArgs e)
224 | {
225 | if (!_boxHost.IsMouseCaptured)
226 | {
227 | _mouseDownPos = e.GetPosition(_boxHost);
228 | _boxHost.CaptureMouse();
229 |
230 | _mouseDownHitBox = GetHitBox(_mouseDownPos);
231 | if (_mouseDownHitBox != null)
232 | _mouseDownHitBox.Focus();
233 | else
234 | Focus();
235 | }
236 | e.Handled = true;
237 | }
238 |
239 | void _boxHost_MouseMove(object sender, MouseEventArgs e)
240 | {
241 | if (_boxHost.IsMouseCaptured)
242 | {
243 | var mousePos = e.GetPosition(_boxHost);
244 |
245 | if (_rubberBand == null)
246 | {
247 | _rubberBand = new Rectangle();
248 | _rubberBand.IsHitTestVisible = false;
249 | _rubberBand.Stroke = Brushes.LightGray;
250 | _rubberBandHost.Children.Add(_rubberBand);
251 | }
252 |
253 | var width = Math.Abs(_mouseDownPos.X - mousePos.X);
254 | var height = Math.Abs(_mouseDownPos.Y - mousePos.Y);
255 | var left = Math.Min(_mouseDownPos.X, mousePos.X);
256 | var top = Math.Min(_mouseDownPos.Y, mousePos.Y);
257 |
258 | _rubberBand.Width = width;
259 | _rubberBand.Height = height;
260 | Canvas.SetLeft(_rubberBand, left);
261 | Canvas.SetTop(_rubberBand, top);
262 | }
263 | }
264 |
265 | void _boxHost_MouseLeftButtonUp(object sender, MouseButtonEventArgs e)
266 | {
267 | if (_boxHost.IsMouseCaptured)
268 | {
269 | var mouseUpPos = e.GetPosition(_boxHost);
270 | if (_rubberBand != null)
271 | {
272 | _rubberBandHost.Children.Remove(_rubberBand);
273 | _rubberBand = null;
274 | }
275 | _boxHost.ReleaseMouseCapture();
276 |
277 | var selectRec = new Rect(_mouseDownPos, mouseUpPos);
278 | //if they are holding one of the ctrl keys, make a new box...
279 | if (Keyboard.IsKeyDown(Key.LeftCtrl) || Keyboard.IsKeyDown(Key.RightCtrl))
280 | {
281 | if (selectRec.Size.Width > 2 && selectRec.Height > 2)
282 | {
283 | var hanlder = CreateBox;
284 | if(hanlder != null)
285 | hanlder.Invoke(this, selectRec);
286 | }
287 | }
288 | else
289 | {
290 | var mouseUpHitBox = GetHitBox(mouseUpPos);
291 | if (_mouseDownHitBox == null)
292 | {
293 | var boxes = GetHitBoxes(selectRec);
294 | SelectedItems.Clear();
295 | foreach (var box in boxes)
296 | SelectedItems.Add(box);
297 | }
298 | else
299 | {
300 | if (_mouseDownHitBox == mouseUpHitBox)
301 | {
302 | //mouse down and up on the same box!
303 | if (!Keyboard.IsKeyDown(Key.LeftCtrl) && !Keyboard.IsKeyDown(Key.RightCtrl))
304 | SelectedItems.Clear();
305 |
306 | if (mouseUpHitBox.IsSelected)
307 | SelectedItems.Remove(mouseUpHitBox);
308 | else
309 | SelectedItems.Add(mouseUpHitBox);
310 | }
311 | else
312 | {
313 | var boxes = GetHitBoxes(selectRec);
314 | SelectedItems.Clear();
315 | foreach (var box in boxes)
316 | SelectedItems.Add(box);
317 | }
318 | }
319 | }
320 |
321 | _mouseDownHitBox = null;
322 | }
323 | e.Handled = true;
324 | }
325 |
326 | protected override void OnSelectionChanged(SelectionChangedEventArgs e)
327 | {
328 | base.OnSelectionChanged(e);
329 |
330 | foreach (TessBoxControl box in e.RemovedItems)
331 | box.IsSelected = false;
332 |
333 | foreach (TessBoxControl box in e.AddedItems)
334 | box.IsSelected = true;
335 |
336 | }
337 |
338 | protected override DependencyObject GetContainerForItemOverride()
339 | {
340 | return new TessBoxControl();
341 | }
342 |
343 | protected override bool IsItemItsOwnContainerOverride(object item)
344 | {
345 | return item is TessBoxControl;
346 | }
347 |
348 | protected override void PrepareContainerForItemOverride(DependencyObject element, object item)
349 | {
350 | base.PrepareContainerForItemOverride(element, item);
351 | }
352 |
353 | }
354 |
355 | public delegate void CreateBoxHandler(object sender, Rect bounds);
356 | }
357 |
--------------------------------------------------------------------------------
/BoxFileEditor/Images/Create.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/scotts48/tesseract-box-editor/904fb4a6c2c72809883283378215dd8709704410/BoxFileEditor/Images/Create.png
--------------------------------------------------------------------------------
/BoxFileEditor/Images/Delete.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/scotts48/tesseract-box-editor/904fb4a6c2c72809883283378215dd8709704410/BoxFileEditor/Images/Delete.png
--------------------------------------------------------------------------------
/BoxFileEditor/Images/DeleteHS.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/scotts48/tesseract-box-editor/904fb4a6c2c72809883283378215dd8709704410/BoxFileEditor/Images/DeleteHS.png
--------------------------------------------------------------------------------
/BoxFileEditor/Images/Link.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/scotts48/tesseract-box-editor/904fb4a6c2c72809883283378215dd8709704410/BoxFileEditor/Images/Link.png
--------------------------------------------------------------------------------
/BoxFileEditor/Images/OpenHS.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/scotts48/tesseract-box-editor/904fb4a6c2c72809883283378215dd8709704410/BoxFileEditor/Images/OpenHS.png
--------------------------------------------------------------------------------
/BoxFileEditor/Images/SaveHS.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/scotts48/tesseract-box-editor/904fb4a6c2c72809883283378215dd8709704410/BoxFileEditor/Images/SaveHS.png
--------------------------------------------------------------------------------
/BoxFileEditor/MainViewModel.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.Collections.Generic;
3 | using System.Collections.ObjectModel;
4 | using System.ComponentModel;
5 | using System.IO;
6 | using System.Linq;
7 | using System.Text;
8 | using System.Windows;
9 | using System.Windows.Controls;
10 | using System.Windows.Media;
11 | using System.Windows.Media.Imaging;
12 | using System.Windows.Shapes;
13 | using Path = System.IO.Path;
14 |
15 | namespace BoxFileEditor
16 | {
17 | public class MainViewModel : INotifyPropertyChanged
18 | {
19 | public const string AppTitle = "Tesseract Box Editor";
20 |
21 | private string _imageFileName = null;
22 | private string _boxFileName = null;
23 |
24 | private FileStream _fileStream = null;
25 | private TiffBitmapDecoder _tiffDecoder = null;
26 |
27 | private Dictionary> _pageBoxMap = new Dictionary>();
28 | private Dictionary _pageImageMap = new Dictionary();
29 |
30 | public string WindowTitle
31 | {
32 | get
33 | {
34 | if (string.IsNullOrEmpty(_imageFileName))
35 | return AppTitle;
36 | return string.Format("{0} - {1}", Path.GetFileName(_imageFileName), AppTitle);
37 | }
38 | }
39 |
40 | private int _maxPageIndex = 0;
41 | public int MaxPageIndex
42 | {
43 | get { return _maxPageIndex; }
44 | private set
45 | {
46 | if (_maxPageIndex == value) return;
47 | _maxPageIndex = value;
48 | NotifyPropertyChanged("MaxPageIndex");
49 | }
50 | }
51 |
52 | private int _selPageIndex = 0;
53 | public int SelPageIndex
54 | {
55 | get { return _selPageIndex; }
56 | set
57 | {
58 | if (_selPageIndex == value) return;
59 | _selPageIndex = value;
60 | SelectPage(value);
61 | NotifyPropertyChanged("SelPageIndex");
62 | }
63 | }
64 |
65 | private BitmapSource _image;
66 | public BitmapSource Image
67 | {
68 | get { return _image; }
69 | set
70 | {
71 | if (_image == value) return;
72 | _image = value;
73 | NotifyPropertyChanged("Image");
74 | }
75 | }
76 |
77 | private CroppedBitmap _selectedBoxImage = null;
78 | public CroppedBitmap SelectedBoxImage
79 | {
80 | get { return _selectedBoxImage; }
81 | set
82 | {
83 | if (_selectedBoxImage == value) return;
84 | _selectedBoxImage = value;
85 | NotifyPropertyChanged("SelectedBoxImage");
86 | }
87 | }
88 |
89 | private ObservableCollection _boxes = null;
90 | public ObservableCollection Boxes
91 | {
92 | get { return _boxes; }
93 | set
94 | {
95 | if(_boxes == value)return;
96 | _boxes = value;
97 | NotifyPropertyChanged("Boxes");
98 | }
99 | }
100 |
101 | private List _selectedBoxes = new List();
102 | public IEnumerable SelectedBoxes
103 | {
104 | get { return _selectedBoxes; }
105 | set
106 | {
107 | var selectedItem = SelectedItem;
108 | if(selectedItem != null)
109 | selectedItem.PropertyChanged -= selectedItem_PropertyChanged;
110 |
111 | _selectedBoxes.Clear();
112 | _selectedBoxes.AddRange(value);
113 | NotifyPropertyChanged("SelectedBoxes");
114 | NotifyPropertyChanged("SelectedItemValue");
115 | NotifyPropertyChanged("SelectedItem");
116 | NotifyPropertyChanged("CanEditSingleBox");
117 |
118 | selectedItem = SelectedItem;
119 | if(selectedItem != null)
120 | selectedItem.PropertyChanged += selectedItem_PropertyChanged;
121 |
122 | UpdateCroppedImage();
123 | }
124 | }
125 |
126 | public bool CanEditSingleBox
127 | {
128 | get { return _selectedBoxes.Count == 1; }
129 | }
130 |
131 | public TessBoxControl SelectedItem
132 | {
133 | get { return _selectedBoxes.Count == 1 ? _selectedBoxes[0] : null; }
134 | }
135 |
136 | public string SelectedItemValue
137 | {
138 | get
139 | {
140 | if (_selectedBoxes.Count == 1)
141 | return _selectedBoxes[0].Value;
142 | else
143 | return string.Empty;
144 | }
145 | set
146 | {
147 | foreach (var box in _selectedBoxes)
148 | {
149 | box.Value = value;
150 | }
151 | }
152 | }
153 |
154 | public event PropertyChangedEventHandler PropertyChanged;
155 |
156 | protected void NotifyPropertyChanged(string propertyName)
157 | {
158 | var handler = PropertyChanged;
159 | if(handler != null)
160 | handler.Invoke(this, new PropertyChangedEventArgs(propertyName));
161 | }
162 |
163 | public MainViewModel()
164 | {
165 | }
166 |
167 | private void SelectPage(int pageIndex)
168 | {
169 | ObservableCollection boxes = null;
170 | if (_pageBoxMap.ContainsKey(pageIndex))
171 | {
172 | boxes = _pageBoxMap[pageIndex];
173 | }
174 | else
175 | {
176 | //if there isn't any boxes for the page, make a new list
177 | boxes = new ObservableCollection();
178 | _pageBoxMap[pageIndex] = boxes;
179 | }
180 | Image = GetPageImage(pageIndex);
181 | Boxes = boxes;
182 | }
183 |
184 | private void UpdateCroppedImage()
185 | {
186 | var selectedItem = SelectedItem;
187 | if (selectedItem == null)
188 | {
189 | SelectedBoxImage = null;
190 | }
191 | else
192 | {
193 | SelectedBoxImage = new CroppedBitmap(Image, new Int32Rect((int)selectedItem.Left, (int)selectedItem.Top, (int)selectedItem.Width, (int)selectedItem.Height));
194 | }
195 | }
196 |
197 | void selectedItem_PropertyChanged(object sender, PropertyChangedEventArgs e)
198 | {
199 | if(e.PropertyName == "RenderSize" || e.PropertyName == "Left" || e.PropertyName == "Top")
200 | UpdateCroppedImage();
201 | }
202 |
203 | private BitmapSource GetPageImage(int pageIndex)
204 | {
205 | BitmapSource bmp = null;
206 |
207 | if (!_pageImageMap.TryGetValue(pageIndex, out bmp))
208 | {
209 | if (_tiffDecoder == null)
210 | {
211 | var srcBmp = new BitmapImage();
212 | srcBmp.BeginInit();
213 | srcBmp.StreamSource = _fileStream;
214 | srcBmp.EndInit();
215 | srcBmp.Freeze();
216 | bmp = srcBmp;
217 | }
218 | else
219 | {
220 | bmp = _tiffDecoder.Frames[pageIndex];
221 | }
222 |
223 | bmp = LoadImageAtWpfDPI(bmp);
224 | _pageImageMap[pageIndex] = bmp;
225 | }
226 | return bmp;
227 | }
228 |
229 | private BitmapSource LoadImageAtWpfDPI(BitmapSource srcBmp)
230 | {
231 | WriteableBitmap bmp = null;
232 | {
233 | //we need the bitmap at WPF dpi so that all our position stuff works out. It's a lot easier
234 | //to do this, than try and make controls that handle pixel stuff
235 | bmp = new WriteableBitmap(srcBmp.PixelWidth, srcBmp.PixelHeight, 96, 96, srcBmp.Format, srcBmp.Palette);
236 | int stride = BitmapHelper.GetBitmapDataStride(srcBmp.Format.BitsPerPixel, srcBmp.PixelWidth);
237 | int byteCount = BitmapHelper.GetBitmapDataByteCount(srcBmp.Format.BitsPerPixel, srcBmp.PixelWidth, srcBmp.PixelHeight);
238 | var pixels = new byte[byteCount];
239 | srcBmp.CopyPixels(pixels, stride, 0);
240 | bmp.WritePixels(new Int32Rect(0, 0, srcBmp.PixelWidth, srcBmp.PixelHeight), pixels, stride, 0);
241 | bmp.Freeze();
242 | }
243 | return bmp;
244 | }
245 |
246 | public void Close()
247 | {
248 | _tiffDecoder = null;
249 | if (_fileStream != null)
250 | {
251 | _fileStream.Dispose();
252 | _fileStream = null;
253 | }
254 | Image = null;
255 | Boxes = null;
256 | _imageFileName = null;
257 | _boxFileName = null;
258 | _maxPageIndex = 0;
259 | _selPageIndex = 0;
260 | _pageBoxMap.Clear();
261 | _pageImageMap.Clear();
262 | NotifyPropertyChanged("WindowTitle");
263 | }
264 |
265 | public void Load(string imageFileName)
266 | {
267 | var fi = new FileInfo(imageFileName);
268 | var boxFileName = Path.Combine(fi.DirectoryName, Path.GetFileNameWithoutExtension(fi.Name) + ".box");
269 |
270 | if (!File.Exists(boxFileName))
271 | throw new ApplicationException(string.Format("Box file '{0}' was not found", Path.GetFileName(boxFileName)));
272 |
273 | Close();
274 |
275 | _fileStream = new FileStream(imageFileName, FileMode.Open, FileAccess.Read);
276 | if (TiffHelper.IsTiffFile(_fileStream))
277 | {
278 | _tiffDecoder = new TiffBitmapDecoder(_fileStream, BitmapCreateOptions.PreservePixelFormat, BitmapCacheOption.Default);
279 | MaxPageIndex = _tiffDecoder.Frames.Count-1;
280 | }
281 | else
282 | {
283 | MaxPageIndex = 0;
284 | }
285 |
286 |
287 | int recordIndex = 0;
288 | using (var reader = new StreamReader(boxFileName, Encoding.UTF8))
289 | {
290 | ObservableCollection boxes = null;
291 | while (true)
292 | {
293 | var line = reader.ReadLine();
294 | if (line == null)
295 | break;
296 | var fields = line.Split(' ');
297 | if (fields.Length != 6)
298 | throw new ApplicationException(string.Format("Invalid box file record at line {0}", recordIndex));
299 |
300 | var boxValue = fields[0];
301 | var pageIndex = int.Parse(fields[5]);
302 | var img = GetPageImage(pageIndex);
303 |
304 | //Box file coordinates are with an origin at the bottom left on the image
305 | var left = int.Parse(fields[1]);
306 | var bottom = img.PixelHeight - int.Parse(fields[2]);
307 | var right = int.Parse(fields[3]);
308 | var top = img.PixelHeight - int.Parse(fields[4]);
309 |
310 | var width = right - left;
311 | var height = bottom - top;
312 |
313 | var box = new TessBoxControl();
314 | box.Value = boxValue;
315 | box.Width = width;
316 | box.Height = height;
317 | Canvas.SetLeft(box, left);
318 | Canvas.SetTop(box, top);
319 |
320 | if (!_pageBoxMap.TryGetValue(pageIndex, out boxes))
321 | {
322 | boxes = new ObservableCollection();
323 | _pageBoxMap[pageIndex] = boxes;
324 | }
325 |
326 | boxes.Add(box);
327 | recordIndex++;
328 | }
329 | }
330 |
331 | _imageFileName = imageFileName;
332 | _boxFileName = boxFileName;
333 | NotifyPropertyChanged("WindowTitle");
334 |
335 | //set manually to force SelPageIndex to accept the change
336 | _selPageIndex = -1;
337 | SelPageIndex = 0;
338 | }
339 |
340 | public void Save()
341 | {
342 | using (var writer = new StreamWriter(_boxFileName, false, new UTF8Encoding(false)))
343 | {
344 | var orderPages = _pageBoxMap.OrderBy(n => n.Key);
345 | foreach (var pageNode in orderPages)
346 | {
347 | var boxes = pageNode.Value;
348 | foreach (var box in boxes)
349 | {
350 | var left = box.Left;
351 | var top = box.Top;
352 | var right = box.Left + box.Width;
353 | var bottom = box.Top + box.Height;
354 |
355 | var img = GetPageImage(pageNode.Key);
356 |
357 | var line = string.Format("{0} {1} {2} {3} {4} {5}", box.Value, left, img.PixelHeight - bottom, right, img.PixelHeight - top, pageNode.Key);
358 | writer.WriteLine(line);
359 | }
360 | }
361 | }
362 | }
363 |
364 | public void MergeSelectedBoxes(IEnumerable selectedBoxes)
365 | {
366 | var bounds = Rect.Empty;
367 | var remove = new List();
368 | foreach (var box in selectedBoxes)
369 | {
370 | var boxBounds = new Rect(box.Left, box.Top, box.Width, box.Height);
371 | if (bounds.IsEmpty)
372 | bounds = boxBounds;
373 | else
374 | bounds.Union(boxBounds);
375 | remove.Add(box);
376 | }
377 | var keep = remove[remove.Count - 1];
378 | remove.RemoveAt(remove.Count - 1);
379 | foreach (var box in remove)
380 | Boxes.Remove(box);
381 |
382 | keep.Left = bounds.Left;
383 | keep.Top = bounds.Top;
384 | keep.Width = bounds.Width;
385 | keep.Height = bounds.Height;
386 | }
387 |
388 | public void DeleteSelectedBoxes(IEnumerable selectedBoxes)
389 | {
390 | var remove = new List(selectedBoxes);
391 | foreach (var box in remove)
392 | Boxes.Remove(box);
393 | }
394 |
395 | }
396 | }
397 |
--------------------------------------------------------------------------------
/BoxFileEditor/MainWindow.xaml:
--------------------------------------------------------------------------------
1 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
25 |
33 |
34 |
35 |
36 |
37 |
38 |
39 |
47 |
55 |
56 |
57 |
58 |
59 |
60 |
61 |
62 |
63 |
64 |
65 |
66 |
67 |
68 |
69 |
70 |
71 |
72 |
73 |
74 |
75 |
76 |
77 |
78 |
79 |
80 |
81 |
82 |
83 |
84 |
85 |
86 |
87 |
88 |
--------------------------------------------------------------------------------
/BoxFileEditor/MainWindow.xaml.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.Collections.Generic;
3 | using System.Linq;
4 | using System.Text;
5 | using System.Windows;
6 | using System.Windows.Controls;
7 | using System.Windows.Data;
8 | using System.Windows.Documents;
9 | using System.Windows.Input;
10 | using System.Windows.Media;
11 | using System.Windows.Media.Imaging;
12 | using System.Windows.Navigation;
13 | using System.Windows.Shapes;
14 | using Microsoft.Win32;
15 |
16 | namespace BoxFileEditor
17 | {
18 | ///
19 | /// Interaction logic for MainWindow.xaml
20 | ///
21 | public partial class MainWindow : Window
22 | {
23 | private SelectedBoxWnd _boxWnd = null;
24 |
25 | private MainViewModel _viewModel = null;
26 |
27 | private bool _suppressEventHandlers = false;
28 |
29 | public MainWindow()
30 | {
31 | InitializeComponent();
32 |
33 | _viewModel = new MainViewModel();
34 | DataContext = _viewModel;
35 |
36 | //System.Windows.Interop.HwndSource.DefaultAcquireHwndFocusInMenuMode = false;
37 | //Keyboard.DefaultRestoreFocusMode = RestoreFocusMode.None;
38 | }
39 |
40 | private void Window_Loaded(object sender, RoutedEventArgs e)
41 | {
42 | _boxWnd = new SelectedBoxWnd();
43 | _boxWnd.Owner = this;
44 | _boxWnd.ShowActivated = false;
45 | _boxWnd.DataContext = _viewModel;
46 | _boxWnd.Show();
47 | /*
48 | _zoomToolWindow = new Window();
49 | _zoomToolWindow.Owner = this;
50 | _zoomToolWindow.WindowStyle = WindowStyle.ToolWindow;
51 | _zoomToolWindow.Background = SystemColors.AppWorkspaceBrush;
52 | _zoomToolWindow.ShowInTaskbar = false;
53 | _zoomToolWindow.Width = 200;
54 | _zoomToolWindow.Height = 200;
55 | _zoomToolWindow.DataContext = DataContext;
56 | _zoomToolWindow.Title = "Selected Box View";
57 |
58 | var img = new Image();
59 | img.Margin = new Thickness(10);
60 | img.Stretch = Stretch.Uniform;
61 | img.SetValue(RenderOptions.BitmapScalingModeProperty, BitmapScalingMode.NearestNeighbor);
62 | img.SetBinding(Image.SourceProperty, new Binding("SelectedBoxImage"));
63 | _zoomToolWindow.Content = img;
64 |
65 | _zoomToolWindow.Show();
66 | */
67 | }
68 |
69 | private void boxView_SelectionChanged(object sender, SelectionChangedEventArgs e)
70 | {
71 | if (!_suppressEventHandlers)
72 | {
73 | _suppressEventHandlers = true;
74 | foreach (var removedItem in e.RemovedItems)
75 | boxList.SelectedItems.Remove(removedItem);
76 | foreach (var addedItem in e.AddedItems)
77 | boxList.SelectedItems.Add(addedItem);
78 | _viewModel.SelectedBoxes = boxView.SelectedItems.Cast();
79 | _suppressEventHandlers = false;
80 | }
81 | if(_viewModel.SelectedItem != null)
82 | boxList.ScrollIntoView(_viewModel.SelectedItem);
83 | }
84 |
85 | private void boxList_SelectionChanged(object sender, SelectionChangedEventArgs e)
86 | {
87 | if (!_suppressEventHandlers)
88 | {
89 | _suppressEventHandlers = true;
90 | foreach (var removedItem in e.RemovedItems)
91 | boxView.SelectedItems.Remove(removedItem);
92 | foreach (var addedItem in e.AddedItems)
93 | boxView.SelectedItems.Add(addedItem);
94 | _viewModel.SelectedBoxes = boxView.SelectedItems.Cast();
95 | _suppressEventHandlers = false;
96 | }
97 | if (_viewModel.SelectedItem != null)
98 | boxView.ScrollIntoView(_viewModel.SelectedItem);
99 | }
100 |
101 | private void btnMerge_Click(object sender, RoutedEventArgs e)
102 | {
103 | _viewModel.MergeSelectedBoxes(boxView.SelectedItems.Cast());
104 | }
105 |
106 | private void btnDelete_Click(object sender, RoutedEventArgs e)
107 | {
108 | _viewModel.DeleteSelectedBoxes(boxView.SelectedItems.Cast());
109 | }
110 |
111 | protected override void OnPreviewKeyDown(KeyEventArgs e)
112 | {
113 | if (e.Key == Key.PageUp)
114 | {
115 | if (_viewModel.SelPageIndex < _viewModel.MaxPageIndex)
116 | _viewModel.SelPageIndex++;
117 | e.Handled = true;
118 | }
119 | else if (e.Key == Key.PageDown)
120 | {
121 | if (_viewModel.SelPageIndex > 0)
122 | _viewModel.SelPageIndex--;
123 | e.Handled = true;
124 | }
125 | base.OnPreviewKeyDown(e);
126 | }
127 |
128 | protected internal void ApplyValueToSelectedBoxes(string value, bool advance)
129 | {
130 | _viewModel.SelectedItemValue = value;
131 | if (advance)
132 | {
133 | var selectedItem = boxView.SelectedItem as TessBoxControl;
134 | if (selectedItem != null)
135 | {
136 | var index = boxView.ItemContainerGenerator.IndexFromContainer(selectedItem);
137 | index++;
138 | selectedItem = boxView.ItemContainerGenerator.ContainerFromIndex(index) as TessBoxControl;
139 | boxView.SelectedItem = selectedItem;
140 | _boxWnd.SelectAndFocusValue();
141 | }
142 | }
143 | }
144 |
145 | private void menuLoad_Click(object sender, RoutedEventArgs e)
146 | {
147 | var openDlg = new OpenFileDialog();
148 | openDlg.Filter = "TIFF Image Files (*.tiff, *.tif)|*.tiff;*.tif|PNG Image Files (*.png)|*.png|All Files (*.*)|*.*";
149 | openDlg.Multiselect = false;
150 | openDlg.CheckPathExists = true;
151 | if (openDlg.ShowDialog() == true)
152 | {
153 | try
154 | {
155 | _viewModel.Load(openDlg.FileName);
156 | }
157 | catch (Exception ex)
158 | {
159 | MessageBox.Show(string.Format("Unable to load '{0}', {1}", openDlg.FileName, ex.GetBaseException().Message), MainViewModel.AppTitle, MessageBoxButton.OK, MessageBoxImage.Error);
160 | }
161 | }
162 | }
163 |
164 | private void menuSave_Click(object sender, RoutedEventArgs e)
165 | {
166 | try
167 | {
168 | _viewModel.Save();
169 | }
170 | catch (Exception ex)
171 | {
172 | MessageBox.Show(string.Format("Unable to save box file, {0}", ex.GetBaseException().Message), MainViewModel.AppTitle, MessageBoxButton.OK, MessageBoxImage.Error);
173 | }
174 | }
175 |
176 | private void boxView_CreateBox(object sender, Rect bounds)
177 | {
178 | boxView.SelectedItems.Clear();
179 |
180 | var box = new TessBoxControl();
181 | box.Value = "?";
182 | box.Width = bounds.Width;
183 | box.Height = bounds.Height;
184 | Canvas.SetLeft(box, bounds.Left);
185 | Canvas.SetTop(box, bounds.Top);
186 | _viewModel.Boxes.Add(box);
187 |
188 | }
189 |
190 | private void boxView_MergeSelected(object sender, EventArgs e)
191 | {
192 | _viewModel.MergeSelectedBoxes(boxView.SelectedItems.Cast());
193 | }
194 |
195 | private void boxView_DeleteSelected(object sender, EventArgs e)
196 | {
197 | _viewModel.DeleteSelectedBoxes(boxView.SelectedItems.Cast());
198 | }
199 |
200 | }
201 | }
202 |
--------------------------------------------------------------------------------
/BoxFileEditor/Properties/AssemblyInfo.cs:
--------------------------------------------------------------------------------
1 | using System.Reflection;
2 | using System.Resources;
3 | using System.Runtime.CompilerServices;
4 | using System.Runtime.InteropServices;
5 | using System.Windows;
6 |
7 | // General Information about an assembly is controlled through the following
8 | // set of attributes. Change these attribute values to modify the information
9 | // associated with an assembly.
10 | [assembly: AssemblyTitle("BoxFileEditor")]
11 | [assembly: AssemblyDescription("")]
12 | [assembly: AssemblyConfiguration("")]
13 | [assembly: AssemblyCompany("")]
14 | [assembly: AssemblyProduct("BoxFileEditor")]
15 | [assembly: AssemblyCopyright("Copyright © 2013")]
16 | [assembly: AssemblyTrademark("")]
17 | [assembly: AssemblyCulture("")]
18 |
19 | // Setting ComVisible to false makes the types in this assembly not visible
20 | // to COM components. If you need to access a type in this assembly from
21 | // COM, set the ComVisible attribute to true on that type.
22 | [assembly: ComVisible(false)]
23 |
24 | //In order to begin building localizable applications, set
25 | //CultureYouAreCodingWith in your .csproj file
26 | //inside a . For example, if you are using US english
27 | //in your source files, set the to en-US. Then uncomment
28 | //the NeutralResourceLanguage attribute below. Update the "en-US" in
29 | //the line below to match the UICulture setting in the project file.
30 |
31 | //[assembly: NeutralResourcesLanguage("en-US", UltimateResourceFallbackLocation.Satellite)]
32 |
33 |
34 | [assembly: ThemeInfo(
35 | ResourceDictionaryLocation.None, //where theme specific resource dictionaries are located
36 | //(used if a resource is not found in the page,
37 | // or application resource dictionaries)
38 | ResourceDictionaryLocation.SourceAssembly //where the generic resource dictionary is located
39 | //(used if a resource is not found in the page,
40 | // app, or any theme specific resource dictionaries)
41 | )]
42 |
43 |
44 | // Version information for an assembly consists of the following four values:
45 | //
46 | // Major Version
47 | // Minor Version
48 | // Build Number
49 | // Revision
50 | //
51 | // You can specify all the values or you can default the Build and Revision Numbers
52 | // by using the '*' as shown below:
53 | // [assembly: AssemblyVersion("1.0.*")]
54 | [assembly: AssemblyVersion("1.0.0.0")]
55 | [assembly: AssemblyFileVersion("1.0.0.0")]
56 |
--------------------------------------------------------------------------------
/BoxFileEditor/Properties/Resources.Designer.cs:
--------------------------------------------------------------------------------
1 | //------------------------------------------------------------------------------
2 | //
3 | // This code was generated by a tool.
4 | // Runtime Version:4.0.30319.18034
5 | //
6 | // Changes to this file may cause incorrect behavior and will be lost if
7 | // the code is regenerated.
8 | //
9 | //------------------------------------------------------------------------------
10 |
11 | namespace BoxFileEditor.Properties
12 | {
13 |
14 |
15 | ///
16 | /// A strongly-typed resource class, for looking up localized strings, etc.
17 | ///
18 | // This class was auto-generated by the StronglyTypedResourceBuilder
19 | // class via a tool like ResGen or Visual Studio.
20 | // To add or remove a member, edit your .ResX file then rerun ResGen
21 | // with the /str option, or rebuild your VS project.
22 | [global::System.CodeDom.Compiler.GeneratedCodeAttribute("System.Resources.Tools.StronglyTypedResourceBuilder", "4.0.0.0")]
23 | [global::System.Diagnostics.DebuggerNonUserCodeAttribute()]
24 | [global::System.Runtime.CompilerServices.CompilerGeneratedAttribute()]
25 | internal class Resources
26 | {
27 |
28 | private static global::System.Resources.ResourceManager resourceMan;
29 |
30 | private static global::System.Globalization.CultureInfo resourceCulture;
31 |
32 | [global::System.Diagnostics.CodeAnalysis.SuppressMessageAttribute("Microsoft.Performance", "CA1811:AvoidUncalledPrivateCode")]
33 | internal Resources()
34 | {
35 | }
36 |
37 | ///
38 | /// Returns the cached ResourceManager instance used by this class.
39 | ///
40 | [global::System.ComponentModel.EditorBrowsableAttribute(global::System.ComponentModel.EditorBrowsableState.Advanced)]
41 | internal static global::System.Resources.ResourceManager ResourceManager
42 | {
43 | get
44 | {
45 | if ((resourceMan == null))
46 | {
47 | global::System.Resources.ResourceManager temp = new global::System.Resources.ResourceManager("BoxFileEditor.Properties.Resources", typeof(Resources).Assembly);
48 | resourceMan = temp;
49 | }
50 | return resourceMan;
51 | }
52 | }
53 |
54 | ///
55 | /// Overrides the current thread's CurrentUICulture property for all
56 | /// resource lookups using this strongly typed resource class.
57 | ///
58 | [global::System.ComponentModel.EditorBrowsableAttribute(global::System.ComponentModel.EditorBrowsableState.Advanced)]
59 | internal static global::System.Globalization.CultureInfo Culture
60 | {
61 | get
62 | {
63 | return resourceCulture;
64 | }
65 | set
66 | {
67 | resourceCulture = value;
68 | }
69 | }
70 | }
71 | }
72 |
--------------------------------------------------------------------------------
/BoxFileEditor/Properties/Resources.resx:
--------------------------------------------------------------------------------
1 |
2 |
3 |
62 |
63 |
64 |
65 |
66 |
67 |
68 |
69 |
70 |
71 |
72 |
73 |
74 |
75 |
76 |
77 |
78 |
79 |
80 |
81 |
82 |
83 |
84 |
85 |
86 |
87 |
88 |
89 |
90 |
91 |
92 |
93 |
94 |
95 |
96 |
97 |
98 |
99 |
100 |
101 |
102 |
103 |
104 |
105 |
106 | text/microsoft-resx
107 |
108 |
109 | 2.0
110 |
111 |
112 | System.Resources.ResXResourceReader, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089
113 |
114 |
115 | System.Resources.ResXResourceWriter, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089
116 |
117 |
--------------------------------------------------------------------------------
/BoxFileEditor/Properties/Settings.Designer.cs:
--------------------------------------------------------------------------------
1 | //------------------------------------------------------------------------------
2 | //
3 | // This code was generated by a tool.
4 | // Runtime Version:4.0.30319.18034
5 | //
6 | // Changes to this file may cause incorrect behavior and will be lost if
7 | // the code is regenerated.
8 | //
9 | //------------------------------------------------------------------------------
10 |
11 | namespace BoxFileEditor.Properties
12 | {
13 |
14 |
15 | [global::System.Runtime.CompilerServices.CompilerGeneratedAttribute()]
16 | [global::System.CodeDom.Compiler.GeneratedCodeAttribute("Microsoft.VisualStudio.Editors.SettingsDesigner.SettingsSingleFileGenerator", "11.0.0.0")]
17 | internal sealed partial class Settings : global::System.Configuration.ApplicationSettingsBase
18 | {
19 |
20 | private static Settings defaultInstance = ((Settings)(global::System.Configuration.ApplicationSettingsBase.Synchronized(new Settings())));
21 |
22 | public static Settings Default
23 | {
24 | get
25 | {
26 | return defaultInstance;
27 | }
28 | }
29 | }
30 | }
31 |
--------------------------------------------------------------------------------
/BoxFileEditor/Properties/Settings.settings:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
--------------------------------------------------------------------------------
/BoxFileEditor/SelectedBoxWnd.xaml:
--------------------------------------------------------------------------------
1 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
24 |
25 |
26 |
27 |
28 |
29 |
30 |
31 |
32 |
33 |
--------------------------------------------------------------------------------
/BoxFileEditor/SelectedBoxWnd.xaml.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.Collections.Generic;
3 | using System.Linq;
4 | using System.Text;
5 | using System.Windows;
6 | using System.Windows.Controls;
7 | using System.Windows.Data;
8 | using System.Windows.Documents;
9 | using System.Windows.Input;
10 | using System.Windows.Media;
11 | using System.Windows.Media.Imaging;
12 | using System.Windows.Shapes;
13 |
14 | namespace BoxFileEditor
15 | {
16 | ///
17 | /// Interaction logic for SelectedBoxWnd.xaml
18 | ///
19 | public partial class SelectedBoxWnd : Window
20 | {
21 | public SelectedBoxWnd()
22 | {
23 | InitializeComponent();
24 | }
25 |
26 | protected override void OnKeyDown(KeyEventArgs e)
27 | {
28 | if (e.Key == Key.Enter)
29 | {
30 | if (btnApplyAndAdvance.IsEnabled)
31 | Dispatcher.BeginInvoke(new RoutedEventHandler(btnApplyAndAdvance_Click), new object[] { this, new RoutedEventArgs() });
32 | e.Handled = true;
33 | }
34 | base.OnKeyDown(e);
35 | }
36 |
37 | private void btnApply_Click(object sender, RoutedEventArgs e)
38 | {
39 | var mainWnd = Owner as MainWindow;
40 | mainWnd.ApplyValueToSelectedBoxes(textBoxValue.Text, false);
41 | }
42 |
43 | private void btnApplyAndAdvance_Click(object sender, RoutedEventArgs e)
44 | {
45 | var mainWnd = Owner as MainWindow;
46 | mainWnd.ApplyValueToSelectedBoxes(textBoxValue.Text, true);
47 | }
48 |
49 | public void SelectAndFocusValue()
50 | {
51 | Dispatcher.BeginInvoke(new Action(AsyncFocusTextBoxValue));
52 | }
53 |
54 | private void AsyncFocusTextBoxValue()
55 | {
56 | textBoxValue.Focus();
57 | textBoxValue.SelectAll();
58 | }
59 |
60 | }
61 | }
62 |
--------------------------------------------------------------------------------
/BoxFileEditor/Themes/Generic.xaml:
--------------------------------------------------------------------------------
1 |
3 |
4 |
5 |
32 |
33 |
57 |
58 |
59 |
60 |
--------------------------------------------------------------------------------
/BoxFileEditor/TiffHelper.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.Collections.Generic;
3 | using System.IO;
4 | using System.Linq;
5 | using System.Text;
6 |
7 | namespace BoxFileEditor
8 | {
9 | class TiffHelper
10 | {
11 | public const UInt16 TiffBigEndian = 0x4d4d;
12 | public const UInt16 TIffLittleEndian = 0x4949;
13 | public const UInt16 TiffVersion = 42;
14 |
15 | public static bool IsTiffFile(Stream inStream)
16 | {
17 | long savePos = inStream.Position;
18 | try
19 | {
20 | //DON'T dispose the reader, it'll close our base stream!
21 | var header = new byte[8];
22 | inStream.Read(header, 0, header.Length);
23 |
24 | var tiffMagic = BitConverter.ToUInt16(header, 0);
25 | var tiffVersion = BitConverter.ToUInt16(header, 2);
26 | ulong firstIFD = BitConverter.ToUInt32(header, 4);
27 | //adjust our endian-ness based on the magic number...
28 | if (tiffMagic == TIffLittleEndian)
29 | {
30 | //file is little endian
31 | }
32 | else if(tiffMagic == TiffBigEndian)
33 | {
34 | tiffVersion = SwapUInt16(tiffVersion);
35 | firstIFD = SwapUInt64(firstIFD);
36 | }
37 | else
38 | {
39 | //throw new Exception("Invalid TIFF Header (Byte Order)");
40 | return false;
41 | }
42 |
43 | //another check to make sure this is really a valid TIFF file...
44 | if(tiffVersion != TiffVersion)
45 | {
46 | //throw new Exception("Invalid TIFF Header (Version)");
47 | return false;
48 | }
49 |
50 | return true;
51 | }
52 | finally
53 | {
54 | inStream.Position = savePos;
55 | }
56 |
57 | }
58 |
59 | public static UInt16 SwapUInt16(UInt16 val)
60 | {
61 | return (UInt16)
62 | (
63 | (UInt16)(val >> 8) |
64 | (UInt16)(val << 8)
65 | );
66 | }
67 |
68 | public static UInt32 SwapUInt32(UInt32 val)
69 | {
70 | return (
71 | ((val & 0x000000ffU) << 24) |
72 | ((val & 0x0000ff00U) << 8) |
73 | ((val & 0x00ff0000U) >> 8) |
74 | ((val & 0xff000000U) >> 24)
75 | );
76 | }
77 |
78 | public static UInt64 SwapUInt64(UInt64 val)
79 | {
80 | return (
81 | ((val & 0x00000000000000ffU) << 56) |
82 | ((val & 0x000000000000ff00U) << 40) |
83 | ((val & 0x0000000000ff0000U) << 24) |
84 | ((val & 0x00000000ff000000U) << 8) |
85 | ((val & 0x000000ff00000000U) >> 8) |
86 | ((val & 0x0000ff0000000000U) >> 24) |
87 | ((val & 0x00ff000000000000U) >> 40) |
88 | ((val & 0xff00000000000000U) >> 56)
89 | );
90 |
91 | }
92 |
93 | }
94 |
95 | }
96 |
--------------------------------------------------------------------------------
/BoxFileEditor/UIHelper.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.Collections.Generic;
3 | using System.Linq;
4 | using System.Text;
5 | using System.Windows;
6 | using System.Windows.Media;
7 |
8 | namespace BoxFileEditor
9 | {
10 | public class UIHelper
11 | {
12 | public static T GetParent(DependencyObject obj) where T : DependencyObject
13 | {
14 | DependencyObject parent = VisualTreeHelper.GetParent(obj);
15 | while (parent != null)
16 | {
17 | if (parent.GetType() == typeof(T))
18 | return parent as T;
19 |
20 | parent = VisualTreeHelper.GetParent(parent);
21 | }
22 | return null;
23 | }
24 |
25 | public static T GetChild(DependencyObject obj) where T : DependencyObject
26 | {
27 | DependencyObject child = null;
28 | for (Int32 i = 0; i < VisualTreeHelper.GetChildrenCount(obj); i++)
29 | {
30 | child = VisualTreeHelper.GetChild(obj, i);
31 | if (child != null && child.GetType() == typeof(T))
32 | {
33 | break;
34 | }
35 | else if (child != null)
36 | {
37 | child = GetChild(child);
38 | if (child != null && child.GetType() == typeof(T))
39 | {
40 | break;
41 | }
42 | }
43 | }
44 | return child as T;
45 | }
46 | }
47 | }
48 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | tesseract-box-editor
--------------------------------------------------------------------------------
/SharedBin/WPFToolkit.Extended.dll:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/scotts48/tesseract-box-editor/904fb4a6c2c72809883283378215dd8709704410/SharedBin/WPFToolkit.Extended.dll
--------------------------------------------------------------------------------