├── .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 |