2009-06-18 17:19:06 +00:00
/ *
Embroidery Reader - an application to view . pes embroidery designs
2019-02-25 04:17:28 +00:00
Copyright ( C ) 2019 Nathan Crawford
2009-06-18 17:19:06 +00:00
This program is free software ; you can redistribute it and / or
modify it under the terms of the GNU General Public License
as published by the Free Software Foundation ; either version 2
of the License , or ( at your option ) any later version .
This program is distributed in the hope that it will be useful ,
but WITHOUT ANY WARRANTY ; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE . See the
GNU General Public License for more details .
You should have received a copy of the GNU General Public License
along with this program ; if not , write to the Free Software
Foundation , Inc . , 59 Temple Place - Suite 330 , Boston , MA
02111 - 1307 , USA .
A copy of the full GPL 2 license can be found in the docs directory .
2009-08-17 18:53:06 +00:00
You can contact me at http : //www.njcrawford.com/contact/.
2009-06-18 17:19:06 +00:00
* /
using System ;
using System.Collections.Generic ;
using System.Drawing ;
2016-03-22 23:16:56 +00:00
using System.IO ;
2009-06-18 17:19:06 +00:00
namespace PesFile
{
2017-12-14 21:43:03 +00:00
public class PECFormatException : Exception
2016-03-22 23:48:13 +00:00
{
public PECFormatException ( string message ) : base ( message ) { }
}
2013-12-07 00:02:34 +00:00
2009-06-18 17:19:06 +00:00
public class PesFile
{
2016-03-22 23:48:13 +00:00
private int imageWidth ;
private int imageHeight ;
private string _filename ;
2019-02-23 21:07:41 +00:00
private string designName ;
2013-12-07 00:03:40 +00:00
public List < StitchBlock > blocks = new List < StitchBlock > ( ) ;
2019-02-23 21:07:41 +00:00
private byte [ ] colorList ;
2016-03-22 23:48:13 +00:00
private Point translateStart ;
private UInt16 pesVersion ;
2016-04-12 20:16:08 +00:00
// Native format appears to use 0.1mm steps, for 254 steps per inch
public int NativeDPI = 254 ;
2009-06-18 17:19:06 +00:00
2012-09-28 22:53:54 +00:00
// If set to true, this variable means we couldn't figure out some or
// all of the colors and white will be used instead of those colors.
2009-06-18 17:19:06 +00:00
private bool colorWarning = false ;
private bool formatWarning = false ;
public PesFile ( string filename )
{
OpenFile ( filename ) ;
}
2013-12-04 19:43:50 +00:00
// Returns an Int16 representation of the 12 bit signed int contained in
// high and low bytes
2017-09-13 05:05:29 +00:00
private Int16 Get12Bit2sComplement ( byte high , byte low )
2013-12-04 19:43:50 +00:00
{
Int32 retval ;
// Get the bottom 4 bits of the high byte
retval = high & 0x0f ;
// Shift those bits up where they belong
retval = retval < < 8 ;
// Add in the bottom 8 bits
retval + = low ;
// Check for a negative number (check if 12th bit is 1)
if ( ( retval & 0x0800 ) = = 0x0800 )
{
// Make the number negative by subtracting 4096, which is the
// number of values a 12 bit integer can represent.
// This is a shortcut for getting the 2's complement value.
retval - = 4096 ;
}
return ( Int16 ) retval ;
}
// Returns a signed byte representation of the 7 bit signed int contained
// in b.
2017-09-13 05:05:29 +00:00
private SByte Get7Bit2sComplement ( byte b )
2013-12-04 19:43:50 +00:00
{
SByte retval ;
2013-12-05 04:21:33 +00:00
// Ignore the 8th bit. (make sure it's 0)
2013-12-05 14:35:11 +00:00
b & = 0x7f ;
2013-12-05 04:21:33 +00:00
2013-12-04 19:43:50 +00:00
// Check for a negative number (check if 7th bit is 1)
if ( ( b & 0x40 ) = = 0x40 )
{
// Make the number negative by subtracting 128, which is the
// number of values a 7 bit integer can represent.
// This is a shortcut for getting the 2's complement value.
retval = ( SByte ) ( b - 128 ) ;
}
else
{
// Positive number - no modification needed
retval = ( SByte ) b ;
}
return retval ;
}
2009-06-18 17:19:06 +00:00
private void OpenFile ( string filename )
{
2016-02-24 04:41:24 +00:00
_filename = filename ;
2016-03-22 22:49:30 +00:00
// The using statements ensure fileIn is closed, no matter how the statement is exited.
2016-03-22 23:16:56 +00:00
// Open the file in read-only mode, but allow read-write sharing. This prevents a case where opening
// the file read-only with read sharing could fail because some other application already has it
// open with read-write access even though it's not writing to the file. (I suspect some antivirus
// programs may be doing this) Using this mode may allow reading half-written files, but I don't expect
// that will be an issue with PES designs, since they're generally written once when downloaded and left
// alone after that.
using ( FileStream fileStreamIn = File . Open ( filename , FileMode . Open , FileAccess . Read , FileShare . ReadWrite ) )
2016-02-24 04:41:24 +00:00
{
2016-03-22 23:16:56 +00:00
using ( BinaryReader fileIn = new BinaryReader ( fileStreamIn ) )
2009-06-18 17:19:06 +00:00
{
2016-03-22 22:49:30 +00:00
string startFileSig = "" ;
for ( int i = 0 ; i < 4 ; i + + ) // 4 bytes
2016-02-24 06:46:00 +00:00
{
2016-03-29 03:23:14 +00:00
// This needs to be read as a byte, since characters can be multiple bytes depending on encoding
startFileSig + = ( char ) fileIn . ReadByte ( ) ;
2016-02-24 06:46:00 +00:00
}
2016-03-22 22:49:30 +00:00
if ( startFileSig ! = "#PES" )
2016-02-24 04:41:24 +00:00
{
2016-03-22 22:49:30 +00:00
// This is not a file that we can read
2016-03-22 23:48:13 +00:00
throw new PECFormatException ( "Missing #PES at beginning of file" ) ;
2016-02-24 04:41:24 +00:00
}
2016-03-22 22:49:30 +00:00
// PES version
string versionString = "" ;
for ( int i = 0 ; i < 4 ; i + + ) // 4 bytes
2016-02-24 06:46:00 +00:00
{
2016-03-29 03:23:14 +00:00
// This needs to be read as a byte, since characters can be multiple bytes depending on encoding
versionString + = ( char ) fileIn . ReadByte ( ) ;
2016-02-24 06:46:00 +00:00
}
2016-03-22 22:49:30 +00:00
if ( ! UInt16 . TryParse ( versionString , out pesVersion ) )
2009-06-18 17:19:06 +00:00
{
2016-03-22 22:49:30 +00:00
// This is not a file that we can read
2016-03-22 23:48:13 +00:00
throw new PECFormatException ( "PES version is not the correct format" ) ;
2009-06-18 17:19:06 +00:00
}
2016-03-22 22:49:30 +00:00
int pecstart = fileIn . ReadInt32 ( ) ;
// Sanity check on PEC start position
2019-02-23 21:07:41 +00:00
if ( fileIn . BaseStream . Length < ( pecstart + 512 + 20 ) )
2016-02-24 04:41:24 +00:00
{
2016-03-22 22:49:30 +00:00
// This file is probably truncated
2016-03-22 23:48:13 +00:00
throw new PECFormatException ( "File appears to be truncated (PEC section is beyond end of file)" ) ;
2009-06-18 17:19:06 +00:00
}
2012-08-21 20:05:41 +00:00
2019-02-23 21:07:41 +00:00
// Jump to the PEC section
fileIn . BaseStream . Position = pecstart ;
// Read design name - always preceded by "LA:" as far as I can tell
string pecIntro = "" ;
pecIntro + = ( char ) fileIn . ReadByte ( ) ;
pecIntro + = ( char ) fileIn . ReadByte ( ) ;
pecIntro + = ( char ) fileIn . ReadByte ( ) ;
if ( pecIntro ! = "LA:" )
{
throw new PECFormatException ( "Missing 'LA:' at beginning of PEC header" ) ;
}
designName = "" ;
for ( int i = 0 ; i < 16 ; i + + )
{
designName + = ( char ) fileIn . ReadByte ( ) ;
}
// This byte always seems to be 0x0d (13). Could mean a carriage return, or maybe
// it indicates this section is 13 bytes long.
byte afterName = fileIn . ReadByte ( ) ;
if ( afterName ! = 0x0d )
{
throw new PECFormatException ( "Byte after design name has unexpected value: " + afterName ) ;
}
// Get rest of design name
designName + = " " ;
for ( int i = 0 ; i < 12 ; i + + )
{
designName + = ( char ) fileIn . ReadByte ( ) ;
}
designName = designName . Trim ( ) ;
// 0xff
byte blockStart1 = fileIn . ReadByte ( ) ;
// 0x00
byte blockStart2 = fileIn . ReadByte ( ) ;
// 0x06
byte blockType1 = fileIn . ReadByte ( ) ;
// 0x26
byte blockType2 = fileIn . ReadByte ( ) ;
if ( ! ( blockStart1 = = 0xff & & blockStart2 = = 0x00 & & blockType1 = = 0x06 & & blockType2 = = 0x26 ) )
{
throw new PECFormatException ( "Block start before color list is unexpected: " + blockStart1 . ToString ( "x2" ) + " " + blockStart2 . ToString ( "x2" ) + " " + blockType1 . ToString ( "x2" ) + " " + blockType2 . ToString ( "x2" ) ) ;
}
// 12 bytes of something, always observed to be 0x20
byte [ ] something1 = fileIn . ReadBytes ( 12 ) ;
//fileIn.BaseStream.Position = pecstart + 48;
// Read number of colors in this design, or perhaps the number of color changes.
// It seems that different digitisers use different meanings for this field.
2016-03-22 22:49:30 +00:00
int numColors = fileIn . ReadByte ( ) + 1 ;
2019-02-23 21:07:41 +00:00
// Read the remaining bytes as a color table
// 512 - 48 - 1 = 463 bytes remaining
colorList = fileIn . ReadBytes ( 463 ) ;
// 0x00
byte anotherBlockStart1 = fileIn . ReadByte ( ) ;
// 0x00
byte anotherBlockStart2 = fileIn . ReadByte ( ) ;
if ( ! ( anotherBlockStart1 = = 0x00 & & anotherBlockStart2 = = 0x00 ) )
2016-02-24 04:41:24 +00:00
{
2019-02-23 21:07:41 +00:00
throw new PECFormatException ( "Block start after color list is unexpected: " + anotherBlockStart1 . ToString ( "x2" ) + " " + anotherBlockStart2 . ToString ( "x2" ) ) ;
2016-02-24 04:41:24 +00:00
}
2019-02-23 21:07:41 +00:00
// 4 bytes of something
byte [ ] something2 = fileIn . ReadBytes ( 4 ) ;
// 0xff
byte yetAnotherBlockStart1 = fileIn . ReadByte ( ) ;
// 0xf0
byte yetAnotherBlockStart2 = fileIn . ReadByte ( ) ;
if ( ! ( yetAnotherBlockStart1 = = 0xff & & yetAnotherBlockStart2 = = 0xf0 ) )
{
throw new PECFormatException ( "Block start before stitch blocks begin is unexpected: " + yetAnotherBlockStart1 . ToString ( "x2" ) + " " + yetAnotherBlockStart2 . ToString ( "x2" ) ) ;
}
// 12 bytes of something
byte [ ] something3 = fileIn . ReadBytes ( 12 ) ;
2016-03-22 22:49:30 +00:00
// Read stitch data
2019-02-23 21:07:41 +00:00
//fileIn.BaseStream.Position = pecstart + 512 + 20;
2016-03-22 22:49:30 +00:00
bool thisPartIsDone = false ;
StitchBlock curBlock ;
int prevX = 0 ;
int prevY = 0 ;
int maxX = 0 ;
int minX = 0 ;
int maxY = 0 ;
int minY = 0 ;
2019-02-23 21:07:41 +00:00
int colorNum = 0 ;
2016-03-22 22:49:30 +00:00
int colorIndex = 0 ;
List < Stitch > tempStitches = new List < Stitch > ( ) ;
2016-03-31 04:41:01 +00:00
Stitch prevStitch = null ;
bool firstStitchOfBlock = true ;
2016-03-22 22:49:30 +00:00
while ( ! thisPartIsDone )
2016-02-24 04:41:24 +00:00
{
2016-03-22 22:49:30 +00:00
byte val1 ;
byte val2 ;
val1 = fileIn . ReadByte ( ) ;
val2 = fileIn . ReadByte ( ) ;
if ( val1 = = 0xff & & val2 = = 0x00 )
{
//end of stitches
thisPartIsDone = true ;
//add the last block
curBlock = new StitchBlock ( ) ;
curBlock . stitches = new Stitch [ tempStitches . Count ] ;
tempStitches . CopyTo ( curBlock . stitches ) ;
curBlock . stitchesTotal = tempStitches . Count ;
2017-12-14 21:43:03 +00:00
2019-02-23 21:07:41 +00:00
if ( colorNum > = colorList . Length )
2017-12-14 21:43:03 +00:00
{
2019-02-23 21:07:41 +00:00
throw new IndexOutOfRangeException ( "colorNum (" + colorNum + ") out of range (" + colorList . Length + ") in end of stitches block" ) ;
2017-12-14 21:43:03 +00:00
}
2016-03-22 22:49:30 +00:00
colorIndex = colorList [ colorNum ] ;
2017-12-14 21:43:03 +00:00
2016-03-22 22:49:30 +00:00
curBlock . colorIndex = colorIndex ;
2017-09-13 05:05:29 +00:00
curBlock . color = GetColorFromIndex ( colorIndex ) ;
2016-03-22 22:49:30 +00:00
blocks . Add ( curBlock ) ;
}
else if ( val1 = = 0xfe & & val2 = = 0xb0 )
{
//color switch, start a new block
curBlock = new StitchBlock ( ) ;
curBlock . stitches = new Stitch [ tempStitches . Count ] ;
tempStitches . CopyTo ( curBlock . stitches ) ;
curBlock . stitchesTotal = tempStitches . Count ;
2017-12-14 21:43:03 +00:00
2019-02-23 21:07:41 +00:00
if ( colorNum > = colorList . Length )
2017-12-14 21:43:03 +00:00
{
2019-02-23 21:07:41 +00:00
throw new IndexOutOfRangeException ( "colorNum (" + colorNum + ") out of range (" + colorList . Length + ") in color switch block" ) ;
2017-12-14 21:43:03 +00:00
}
2016-03-22 22:49:30 +00:00
colorIndex = colorList [ colorNum ] ;
2019-02-23 21:07:41 +00:00
colorNum + + ;
2017-12-14 21:43:03 +00:00
2016-03-22 22:49:30 +00:00
curBlock . colorIndex = colorIndex ;
2017-09-13 05:05:29 +00:00
curBlock . color = GetColorFromIndex ( colorIndex ) ;
2016-03-22 22:49:30 +00:00
//read useless(?) byte
2016-03-31 04:41:01 +00:00
// The value of this 'useless' byte seems to start with 2 for the first block and
// alternate between 2 and 1 for every other block after that. The only exception
// I've noted is the last block which appears to always be 0.
2016-03-22 22:49:30 +00:00
curBlock . unknownStartByte = fileIn . ReadByte ( ) ;
blocks . Add ( curBlock ) ;
2016-03-31 04:41:01 +00:00
firstStitchOfBlock = true ;
2016-03-22 22:49:30 +00:00
tempStitches = new List < Stitch > ( ) ;
}
else
{
2016-03-31 00:04:55 +00:00
int deltaX = 0 ;
int deltaY = 0 ;
2016-03-22 22:49:30 +00:00
int extraBits1 = 0x00 ;
2016-03-31 04:41:01 +00:00
Stitch . MoveBitSize xMoveBits ;
Stitch . MoveBitSize yMoveBits ;
2016-03-22 22:49:30 +00:00
if ( ( val1 & 0x80 ) = = 0x80 )
{
// Save the top 4 bits to output with debug info
// The top bit means this is a 12 bit value, but I don't know what the next 3 bits mean.
2016-03-31 00:04:55 +00:00
// The only combinations I've observed in real files are 0x10 and 0x20. 0x20 occurs
// about 4 times as much as 0x10 in the samples I have available.
extraBits1 = val1 & 0x70 ;
2016-03-22 22:49:30 +00:00
// This is a 12-bit int. Allows for needle movement
// of up to +2047 or -2048.
2017-09-13 05:05:29 +00:00
deltaX = Get12Bit2sComplement ( val1 , val2 ) ;
2016-03-22 22:49:30 +00:00
2016-03-31 04:41:01 +00:00
xMoveBits = Stitch . MoveBitSize . TwelveBits ;
2016-03-22 22:49:30 +00:00
// The X value used both bytes, so read next byte
// for Y value.
val1 = fileIn . ReadByte ( ) ;
}
else
{
// This is a 7-bit int. Allows for needle movement
// of up to +63 or -64.
2017-09-13 05:05:29 +00:00
deltaX = Get7Bit2sComplement ( val1 ) ;
2016-03-22 22:49:30 +00:00
2016-03-31 04:41:01 +00:00
xMoveBits = Stitch . MoveBitSize . SevenBits ;
2016-03-22 22:49:30 +00:00
// The X value only used 1 byte, so copy the second
// to to the first for Y value.
val1 = val2 ;
}
int extraBits2 = 0x00 ;
if ( ( val1 & 0x80 ) = = 0x80 )
{
// Save the top 4 bits to output with debug info
// The top bit means this is a 12 bit value, but I don't know what the next 3 bits mean.
// In all the files I've checked, extraBits2 is the same as extraBits1.
2016-03-31 00:04:55 +00:00
extraBits2 = val1 & 0x70 ;
2016-03-22 22:49:30 +00:00
// This is a 12-bit int. Allows for needle movement
// of up to +2047 or -2048.
// Read in the next byte to get the full value
val2 = fileIn . ReadByte ( ) ;
2017-09-13 05:05:29 +00:00
deltaY = Get12Bit2sComplement ( val1 , val2 ) ;
2016-03-31 04:41:01 +00:00
yMoveBits = Stitch . MoveBitSize . TwelveBits ;
2016-03-22 22:49:30 +00:00
}
else
{
// This is a 7-bit int. Allows for needle movement
// of up to +63 or -64.
2017-09-13 05:05:29 +00:00
deltaY = Get7Bit2sComplement ( val1 ) ;
2016-03-31 04:41:01 +00:00
yMoveBits = Stitch . MoveBitSize . SevenBits ;
2016-03-22 22:49:30 +00:00
// Finished reading data for this stitch, no more
// bytes needed.
}
2016-03-31 04:41:01 +00:00
Stitch . StitchType stitchType ;
if ( deltaX = = 0 & & deltaY = = 0 )
{
// A stitch with zero movement seems to indicate the current and next stitches are movement only.
// Almost always occurs at the end of a block.
stitchType = Stitch . StitchType . MovementBeginAnchor ;
}
else if ( extraBits1 = = 0x20 & & extraBits2 = = 0x20 )
{
// A stitch with extrabits 0x20 seems to indicate the current and next stitches are movement only.
// Almost always occurs within a block, not the beginning or end.
stitchType = Stitch . StitchType . MovementOnly ;
}
else if ( extraBits1 = = 0x10 & & extraBits2 = = 0x10 )
{
// A stitch with extrabits 0x10 seems to indicate the current stitch is movement only.
// Almost always occurs at the beginning of a block.
stitchType = Stitch . StitchType . MovementEndAnchor ;
}
else if ( prevStitch ! = null & &
( prevStitch . stitchType = = Stitch . StitchType . MovementOnly | |
prevStitch . stitchType = = Stitch . StitchType . MovementBeginAnchor ) )
{
stitchType = Stitch . StitchType . MovementEndAnchor ;
}
else
{
if ( firstStitchOfBlock )
{
// First stitch of block is usually a movement to position the needle in the block
firstStitchOfBlock = false ;
stitchType = Stitch . StitchType . MovementEndAnchor ;
}
else
{
// Assume everything else is a normal stitch
stitchType = Stitch . StitchType . NormalStitch ;
}
}
2016-03-22 22:49:30 +00:00
// Add stitch to list
2016-03-31 04:41:01 +00:00
prevStitch = new Stitch (
2016-03-22 22:49:30 +00:00
new Point ( prevX , prevY ) ,
new Point ( prevX + deltaX , prevY + deltaY ) ,
extraBits1 ,
2016-03-31 04:41:01 +00:00
extraBits2 ,
xMoveBits ,
yMoveBits ,
stitchType
) ;
tempStitches . Add ( prevStitch ) ;
2016-03-22 22:49:30 +00:00
// Calculate new "previous" position
prevX = prevX + deltaX ;
prevY = prevY + deltaY ;
// Update maximum distances
if ( prevX > maxX )
{
maxX = prevX ;
}
else if ( prevX < minX )
{
minX = prevX ;
}
if ( prevY > maxY )
{
maxY = prevY ;
}
else if ( prevY < minY )
{
minY = prevY ;
}
}
2016-02-24 04:41:24 +00:00
}
2016-03-22 22:49:30 +00:00
imageWidth = maxX - minX ;
imageHeight = maxY - minY ;
translateStart . X = - minX ;
translateStart . Y = - minY ;
2009-06-18 17:19:06 +00:00
}
}
}
public int GetWidth ( )
{
return imageWidth ;
}
public int GetHeight ( )
{
return imageHeight ;
}
public string GetFileName ( )
{
if ( _filename = = null )
{
return "" ;
}
else
{
return _filename ;
}
}
2019-02-23 21:07:41 +00:00
public string GetInternalDesignName ( )
{
return designName ;
}
2013-12-07 00:14:15 +00:00
// Returns the path of the file it saved debug info to
2017-09-13 05:05:29 +00:00
public string SaveDebugInfo ( )
2009-06-18 17:19:06 +00:00
{
2013-12-07 00:14:15 +00:00
string retval = System . IO . Path . ChangeExtension ( _filename , ".txt" ) ;
2016-03-31 04:41:01 +00:00
using ( System . IO . StreamWriter outfile = new System . IO . StreamWriter ( retval ) )
{
2017-09-13 05:05:29 +00:00
outfile . Write ( GetDebugInfo ( ) ) ;
2016-03-31 04:41:01 +00:00
outfile . Close ( ) ;
}
2013-12-07 00:14:15 +00:00
return retval ;
2009-06-18 17:19:06 +00:00
}
2017-09-13 05:05:29 +00:00
public string GetDebugInfo ( )
2009-06-18 17:19:06 +00:00
{
System . IO . StringWriter outfile = new System . IO . StringWriter ( ) ;
2016-03-31 04:41:01 +00:00
outfile . WriteLine ( _filename ) ;
2012-09-28 22:53:54 +00:00
outfile . WriteLine ( "PES version:\t" + pesVersion ) ;
2019-02-23 21:07:41 +00:00
outfile . WriteLine ( "Internal name:\t" + designName ) ;
outfile . WriteLine ( "Format warning:\t" + formatWarning ) ;
outfile . WriteLine ( "Color warning:\t" + colorWarning ) ;
2009-06-18 17:19:06 +00:00
outfile . WriteLine ( "block info" ) ;
outfile . WriteLine ( "number\tcolor\tstitches" ) ;
for ( int i = 0 ; i < this . blocks . Count ; i + + )
{
outfile . WriteLine ( ( i + 1 ) . ToString ( ) + "\t" + blocks [ i ] . colorIndex . ToString ( ) + "\t" + blocks [ i ] . stitchesTotal . ToString ( ) ) ;
}
outfile . WriteLine ( "color table" ) ;
2019-02-23 21:07:41 +00:00
outfile . WriteLine ( "block number\tcolor" ) ;
for ( int i = 0 ; i < colorList . Length ; i + + )
2009-06-18 17:19:06 +00:00
{
2019-02-23 21:07:41 +00:00
outfile . WriteLine ( ( i + 1 ) . ToString ( ) + "\t" + colorList [ i ] ) ;
2009-06-18 17:19:06 +00:00
}
if ( blocks . Count > 0 )
{
outfile . WriteLine ( "Extended stitch debug info" ) ;
2016-03-31 00:04:55 +00:00
int blockCount = 1 ;
foreach ( StitchBlock thisBlock in blocks )
2009-06-18 17:19:06 +00:00
{
2016-03-31 00:04:55 +00:00
outfile . WriteLine ( "block " + blockCount . ToString ( ) + " start (color index " + thisBlock . colorIndex + ")" ) ;
outfile . WriteLine ( "unknown start byte: " + thisBlock . unknownStartByte . ToString ( "X2" ) ) ;
foreach ( Stitch thisStitch in thisBlock . stitches )
2009-06-18 17:19:06 +00:00
{
2017-09-13 05:05:29 +00:00
string tempLine = thisStitch . a . ToString ( ) + " - " + thisStitch . b . ToString ( ) + ", length " + thisStitch . CalcLength ( ) . ToString ( "F02" ) ;
2016-03-31 00:04:55 +00:00
if ( thisStitch . extraBits1 ! = 0x00 )
2016-02-24 06:46:00 +00:00
{
2016-03-31 00:04:55 +00:00
tempLine + = " (extra bits 1: " + thisStitch . extraBits1 . ToString ( "X2" ) + ")" ;
2016-02-24 06:46:00 +00:00
}
2016-03-31 00:04:55 +00:00
if ( thisStitch . extraBits2 ! = 0x00 )
2016-02-24 06:46:00 +00:00
{
2016-03-31 00:04:55 +00:00
tempLine + = " (extra bits 2: " + thisStitch . extraBits2 . ToString ( "X2" ) + ")" ;
2016-02-24 06:46:00 +00:00
}
2016-03-31 04:41:01 +00:00
if ( thisStitch . XMoveBits = = Stitch . MoveBitSize . SevenBits )
{
tempLine + = " (7 bit X move)" ;
}
else if ( thisStitch . XMoveBits = = Stitch . MoveBitSize . TwelveBits )
{
tempLine + = " (12 bit X move)" ;
}
if ( thisStitch . YMoveBits = = Stitch . MoveBitSize . SevenBits )
{
tempLine + = " (7 bit Y move)" ;
}
else if ( thisStitch . YMoveBits = = Stitch . MoveBitSize . TwelveBits )
{
tempLine + = " (12 bit Y move)" ;
}
tempLine + = " (type " + thisStitch . stitchType + ")" ;
2016-02-24 06:46:00 +00:00
outfile . WriteLine ( tempLine ) ;
2009-06-18 17:19:06 +00:00
}
2016-03-31 00:04:55 +00:00
blockCount + + ;
2009-06-18 17:19:06 +00:00
}
}
outfile . Close ( ) ;
return outfile . ToString ( ) ;
}
2017-09-13 05:05:29 +00:00
public bool GetColorWarning ( )
2009-06-18 17:19:06 +00:00
{
return colorWarning ;
}
2017-09-13 05:05:29 +00:00
public bool GetFormatWarning ( )
2009-06-18 17:19:06 +00:00
{
return formatWarning ;
}
2017-09-13 05:05:29 +00:00
private Color GetColorFromIndex ( int index )
2009-06-18 17:19:06 +00:00
{
2012-09-28 22:23:10 +00:00
if ( index > = 1 & & index < = 64 )
{
return Color . FromArgb (
2012-09-30 01:26:32 +00:00
PesColors . colorMap [ index , 0 ] ,
PesColors . colorMap [ index , 1 ] ,
PesColors . colorMap [ index , 2 ]
2012-09-28 22:23:10 +00:00
) ;
}
else
{
2013-10-10 21:25:23 +00:00
colorWarning = true ;
2012-09-28 22:23:10 +00:00
return Color . White ;
}
2009-06-18 17:19:06 +00:00
}
2017-09-13 05:05:29 +00:00
struct OptimizedBlockData
2016-04-05 01:18:12 +00:00
{
public Color color ;
public Point [ ] points ;
2017-09-13 05:05:29 +00:00
public OptimizedBlockData ( Color color , Point [ ] points )
2016-04-05 01:18:12 +00:00
{
this . color = color ;
this . points = points ;
}
}
2017-09-13 05:05:29 +00:00
private List < OptimizedBlockData > GetOptimizedDrawData ( StitchBlock block , float scale , bool filterUglyStitches , double filterUglyStitchesThreshold )
2016-04-05 01:18:12 +00:00
{
2017-09-13 05:05:29 +00:00
List < OptimizedBlockData > retval = new List < OptimizedBlockData > ( ) ;
2016-04-05 01:18:12 +00:00
// Skip this block if it doesn't have stitches, for any reason
if ( block . stitches . Length = = 0 )
{
return retval ;
}
// Start first block
List < Point > currentPoints = new List < Point > ( ) ;
foreach ( Stitch thisStitch in block . stitches )
{
if ( filterUglyStitches & & // Check for filter ugly stitches option
! formatWarning & & // Only filter stitches if we think we understand the format
2017-09-13 05:05:29 +00:00
thisStitch . CalcLength ( ) > filterUglyStitchesThreshold ) // Check stitch length
2016-04-05 01:18:12 +00:00
{
// This stitch is too long, so skip it and start a new block
if ( currentPoints . Count ! = 0 )
{
2017-09-13 05:05:29 +00:00
retval . Add ( new OptimizedBlockData ( block . color , currentPoints . ToArray ( ) ) ) ;
2016-04-05 01:18:12 +00:00
}
currentPoints = new List < Point > ( ) ;
continue ;
}
// Style special stitches differently
// TODO: Finish figuring out the remaining stitch types
if ( filterUglyStitches & & ( thisStitch . stitchType = = Stitch . StitchType . MovementBeginAnchor | |
thisStitch . stitchType = = Stitch . StitchType . MovementOnly | |
thisStitch . stitchType = = Stitch . StitchType . MovementEndAnchor ) )
{
// Skip these stitch types, and start a new block
if ( currentPoints . Count ! = 0 )
{
2017-09-13 05:05:29 +00:00
retval . Add ( new OptimizedBlockData ( block . color , currentPoints . ToArray ( ) ) ) ;
2016-04-05 01:18:12 +00:00
}
currentPoints = new List < Point > ( ) ;
continue ;
}
if ( currentPoints . Count = = 0 )
{
// If this is the first point in this optimized block, we'll need the previous point to form a line
currentPoints . Add ( new Point ( ( int ) ( thisStitch . a . X * scale ) , ( int ) ( thisStitch . a . Y * scale ) ) ) ;
}
currentPoints . Add ( new Point ( ( int ) ( thisStitch . b . X * scale ) , ( int ) ( thisStitch . b . Y * scale ) ) ) ;
}
if ( currentPoints . Count ! = 0 )
{
2017-09-13 05:05:29 +00:00
retval . Add ( new OptimizedBlockData ( block . color , currentPoints . ToArray ( ) ) ) ;
2016-04-05 01:18:12 +00:00
}
return retval ;
}
2016-04-12 20:16:08 +00:00
// When scale is 1.0, each pixel appears to be 0.1mm, or about 254 ppi
2017-09-13 05:05:29 +00:00
public Bitmap DesignToBitmap ( Single threadThickness , bool filterUglyStitches , double filterUglyStitchesThreshold , float scale )
2009-06-18 17:19:06 +00:00
{
2016-04-05 02:10:04 +00:00
// Do some basic input checks
if ( scale < 0.0000001f )
{
throw new ArgumentException ( "Scale must be > 0" ) ;
}
if ( filterUglyStitchesThreshold < 1.0 )
{
throw new ArgumentException ( "Filter ungly stitches threshold must be at least 1.0" ) ;
}
if ( threadThickness < 0.1 )
{
throw new ArgumentException ( "Thread thickness must be at least 0.1" ) ;
}
2015-04-03 06:09:59 +00:00
int imageWidth = ( int ) ( ( GetWidth ( ) + ( threadThickness * 2 ) ) * scale ) ;
int imageHeight = ( int ) ( ( GetHeight ( ) + ( threadThickness * 2 ) ) * scale ) ;
float tempThreadThickness = threadThickness * scale ;
2016-04-07 18:36:19 +00:00
// This assumes the image format will use 4 bytes per pixel
UInt64 imageRequiredRam = ( UInt64 ) ( imageHeight * imageWidth * 4 ) ;
// Check for a bitmap that might be too large.
// Apparently, there is a 2GB (minus a little overhead) limit for objects on the GC heap in
// .Net, even when using a 64 bit framework. More detail at:
// https://blogs.msdn.microsoft.com/joshwil/2005/08/10/bigarrayt-getting-around-the-2gb-array-size-limit/
if ( imageRequiredRam > ( Int32 . MaxValue - 1024 ) )
{
throw new ArgumentOutOfRangeException ( "Generated image would be too large (" + imageWidth + "x" + imageHeight + ", " + imageRequiredRam + " bytes)" ) ;
}
2016-03-31 00:29:26 +00:00
Bitmap DrawArea = new Bitmap ( imageWidth , imageHeight ) ;
// Return now if for any reason there aren't any blocks
if ( blocks . Count = = 0 )
{
return DrawArea ;
}
using ( Graphics xGraph = Graphics . FromImage ( DrawArea ) )
2013-12-07 00:05:15 +00:00
{
2015-04-03 06:09:59 +00:00
int translateX = ( int ) ( translateStart . X * scale ) ;
int translateY = ( int ) ( translateStart . Y * scale ) ;
xGraph . TranslateTransform ( tempThreadThickness + translateX , tempThreadThickness + translateY ) ;
2013-12-11 00:33:45 +00:00
// Draw smoother lines
2013-12-07 00:02:34 +00:00
xGraph . SmoothingMode = System . Drawing . Drawing2D . SmoothingMode . HighQuality ;
2013-12-11 00:33:45 +00:00
2016-03-31 00:29:26 +00:00
// Only use one pen - I think this will be more resource friendly than creating a new pen for each block
using ( Pen tempPen = new Pen ( blocks [ 0 ] . color , tempThreadThickness ) )
2009-06-18 17:19:06 +00:00
{
2016-03-31 00:29:26 +00:00
// Set rounded ends and joints
tempPen . StartCap = System . Drawing . Drawing2D . LineCap . Round ;
tempPen . EndCap = System . Drawing . Drawing2D . LineCap . Round ;
tempPen . LineJoin = System . Drawing . Drawing2D . LineJoin . Round ;
2013-12-11 00:33:45 +00:00
2016-04-05 01:18:12 +00:00
// List to build up draw-optimized data
2017-09-13 05:05:29 +00:00
List < OptimizedBlockData > optimizedBlocks = new List < OptimizedBlockData > ( ) ;
2016-04-05 01:18:12 +00:00
// Get optimized data
2016-03-31 00:29:26 +00:00
foreach ( StitchBlock thisBlock in blocks )
{
2017-09-13 05:05:29 +00:00
optimizedBlocks . AddRange ( GetOptimizedDrawData ( thisBlock , scale , filterUglyStitches , filterUglyStitchesThreshold ) ) ;
2016-04-05 01:18:12 +00:00
}
2016-03-31 04:41:01 +00:00
2016-04-05 01:18:12 +00:00
// Draw using optimized data
2017-09-13 05:05:29 +00:00
foreach ( OptimizedBlockData optBlock in optimizedBlocks )
2016-04-05 01:18:12 +00:00
{
tempPen . Color = optBlock . color ;
xGraph . DrawLines ( tempPen , optBlock . points ) ;
2013-12-07 00:02:34 +00:00
}
2016-04-05 01:18:12 +00:00
// Done with optimized data
optimizedBlocks = null ;
2009-06-18 17:19:06 +00:00
}
}
2015-04-17 03:20:19 +00:00
2009-06-18 17:19:06 +00:00
return DrawArea ;
}
}
}