2019-10-08 15:03:37 +01:00
using AForge.Imaging.Filters ;
using System ;
2019-09-09 00:51:36 +01:00
using System.Drawing ;
2019-11-19 11:31:53 +00:00
using System.Drawing.Drawing2D ;
2019-09-09 00:51:36 +01:00
using System.Drawing.Imaging ;
using System.IO ;
2019-10-04 13:39:41 +01:00
using System.Threading.Tasks ;
2019-10-04 11:18:17 +01:00
using System.Timers ;
using System.Windows ;
using liblogtiny ;
using libpaperang ;
using libpaperang.Interfaces ;
using libpaperang.Main ;
2019-11-19 11:31:53 +00:00
using System.Windows.Forms ;
2019-09-09 00:51:36 +01:00
2019-11-19 11:31:53 +00:00
namespace PaperangPad {
/// <summary>
/// Interaction logic for MainWindow.xaml
/// </summary>
2019-08-22 00:55:40 +01:00
public partial class MainWindow : Window {
2019-10-04 16:59:57 +01:00
//TODO memory optimisation - this thing is a hongery boi
2019-09-30 13:51:15 +01:00
private ILogTiny logger ;
2019-09-28 23:51:46 +01:00
private BaseTypes . Connection mmjcx = BaseTypes . Connection . USB ;
2019-10-03 00:28:27 +01:00
private BaseTypes . Model mmjmd = BaseTypes . Model . None ;
2019-10-04 21:14:50 +01:00
private IPrinter prtr = new USB ( BaseTypes . Model . None ) ;
2019-10-02 16:31:46 +01:00
private Paperang mmj = null ;
private System . Timers . Timer usbpoll ;
2019-10-04 21:14:50 +01:00
byte [ , ] bayer2 = {
{ 0 , 2 } ,
{ 3 , 1 }
} ;
byte [ , ] bayer3 = {
{ 0 , 7 , 3 } ,
{ 6 , 5 , 2 } ,
{ 4 , 1 , 8 }
} ;
byte [ , ] bayer4 = {
{ 0 , 8 , 2 , 10 } ,
{ 12 , 4 , 14 , 6 } ,
{ 3 , 11 , 1 , 9 } ,
{ 15 , 7 , 13 , 5 }
} ;
byte [ , ] bayer8 = {
{ 0 , 48 , 12 , 60 , 3 , 51 , 15 , 63 } ,
{ 32 , 16 , 44 , 28 , 35 , 19 , 47 , 31 } ,
{ 8 , 56 , 4 , 52 , 11 , 59 , 7 , 55 } ,
{ 40 , 24 , 36 , 20 , 43 , 27 , 39 , 23 } ,
{ 2 , 50 , 14 , 62 , 1 , 49 , 13 , 61 } ,
{ 34 , 18 , 46 , 30 , 33 , 17 , 45 , 29 } ,
{ 10 , 58 , 6 , 54 , 9 , 57 , 5 , 53 } ,
{ 42 , 26 , 38 , 22 , 41 , 25 , 37 , 21 }
} ;
2019-10-03 00:29:34 +01:00
//private uint dThresh=127;
2019-10-02 16:31:46 +01:00
private enum AppState {
UnInitNoDev ,
UnInitDev ,
InitDev
} ;
2019-10-04 11:18:17 +01:00
private AppState state = AppState . UnInitDev ;
2019-10-01 21:02:34 +01:00
// TODO: is it out of scope for this library to provide functionality for printing bitmap data?
2019-10-04 21:14:50 +01:00
private byte Clamp ( int v ) = > Convert . ToByte ( v < 0 ? 0 : v > 255 ? 255 : v ) ;
2019-08-22 00:55:40 +01:00
public MainWindow ( ) {
InitializeComponent ( ) ;
2019-09-30 13:51:15 +01:00
logger = new LUITextbox ( ) ;
gMain . DataContext = ( LUITextbox ) logger ;
2019-08-22 00:55:40 +01:00
logger . Info ( "Application started" ) ;
2019-10-02 16:31:46 +01:00
usbpoll = new System . Timers . Timer ( 200 ) {
AutoReset = true
} ;
2019-10-04 11:18:17 +01:00
usbpoll . Elapsed + = EvtUsbPoll ;
2019-10-02 16:31:46 +01:00
usbpoll . Start ( ) ;
logger . Verbose ( "USB presence interval event started" ) ;
2019-11-19 11:31:53 +00:00
byte _szW ;
byte _szH ;
int _szM ;
int _sc ;
2019-10-04 21:14:50 +01:00
//bayer2
_szW = ( byte ) ( bayer2 . GetUpperBound ( 1 ) + 1 ) ;
_szH = ( byte ) ( bayer2 . GetUpperBound ( 0 ) + 1 ) ;
_szM = _szW * _szH ;
_sc = 255 / _szM ;
for ( int mx = 0 ; mx < _szW ; mx + + ) {
for ( int my = 0 ; my < _szH ; my + + )
bayer2 [ mx , my ] = Clamp ( bayer2 [ mx , my ] * _sc ) ;
}
//bayer3
_szW = ( byte ) ( bayer3 . GetUpperBound ( 1 ) + 1 ) ;
_szH = ( byte ) ( bayer3 . GetUpperBound ( 0 ) + 1 ) ;
_szM = _szW * _szH ;
_sc = 255 / _szM ;
for ( int mx = 0 ; mx < _szW ; mx + + ) {
for ( int my = 0 ; my < _szH ; my + + )
bayer3 [ mx , my ] = Clamp ( bayer3 [ mx , my ] * _sc ) ;
}
//bayer4
_szW = ( byte ) ( bayer4 . GetUpperBound ( 1 ) + 1 ) ;
_szH = ( byte ) ( bayer4 . GetUpperBound ( 0 ) + 1 ) ;
_szM = _szW * _szH ;
_sc = 255 / _szM ;
for ( int mx = 0 ; mx < _szW ; mx + + ) {
for ( int my = 0 ; my < _szH ; my + + )
bayer4 [ mx , my ] = Clamp ( bayer4 [ mx , my ] * _sc ) ;
}
//bayer8
_szW = ( byte ) ( bayer8 . GetUpperBound ( 1 ) + 1 ) ;
_szH = ( byte ) ( bayer8 . GetUpperBound ( 0 ) + 1 ) ;
_szM = _szW * _szH ;
_sc = 255 / _szM ;
for ( int mx = 0 ; mx < _szW ; mx + + ) {
for ( int my = 0 ; my < _szH ; my + + )
bayer8 [ mx , my ] = Clamp ( bayer8 [ mx , my ] * _sc ) ;
}
2019-10-02 16:31:46 +01:00
}
2019-10-04 11:18:17 +01:00
private void EvtUsbPoll ( object sender , ElapsedEventArgs e ) = > _ = Dispatcher . BeginInvoke ( new invDgtUsbPoll ( DgtUsbPoll ) ) ;
2019-10-02 16:31:46 +01:00
private delegate void invDgtUsbPoll ( ) ;
2019-10-04 11:18:17 +01:00
private void DgtUsbPoll ( ) {
2019-10-02 16:31:46 +01:00
try {
if ( state = = AppState . UnInitNoDev & & prtr . PrinterAvailable ) {
btInitUSB . IsEnabled = true ;
state = AppState . UnInitDev ;
logger . Info ( "Printer plugged in" ) ;
} else if ( state = = AppState . UnInitDev & & ! prtr . PrinterAvailable ) {
btInitUSB . IsEnabled = false ;
state = AppState . UnInitNoDev ;
logger . Info ( "Printer unplugged" ) ;
} else if ( state = = AppState . InitDev & & ! prtr . PrinterAvailable ) {
logger . Info ( "Printer unplugged while initialised" ) ;
USBDeInit ( ) ;
}
} catch ( Exception ) {
} finally { }
}
~ MainWindow ( ) {
logger . Warn ( "Application closing" ) ;
2019-10-04 11:18:17 +01:00
if ( mmj ! = null )
USBDeInit ( ) ;
2019-08-22 00:55:40 +01:00
}
private void BtClearLog_Click ( object sender , RoutedEventArgs e ) = >
2019-09-30 13:51:15 +01:00
logger . Raw ( "!clearlog" ) ;
2019-10-01 21:02:34 +01:00
private void SetP1_Click ( object sender , RoutedEventArgs e ) {
2019-09-30 13:51:15 +01:00
mmjmd = BaseTypes . Model . P1 ;
2019-10-01 21:02:34 +01:00
logger . Info ( "Model type set to P1 or P1S" ) ;
2019-09-30 13:51:15 +01:00
}
2019-10-01 21:02:34 +01:00
private void SetP2_Click ( object sender , RoutedEventArgs e ) {
2019-09-30 13:51:15 +01:00
mmjmd = BaseTypes . Model . P2 ;
2019-10-01 21:02:34 +01:00
logger . Info ( "Model type set to P2 or P2S" ) ;
}
private void SetT1_Click ( object sender , RoutedEventArgs e ) {
mmjmd = BaseTypes . Model . T1 ;
logger . Info ( "Model type set to T1" ) ;
2019-09-30 13:51:15 +01:00
}
2019-10-02 16:31:46 +01:00
private void BtInitUSB_Click ( object sender , RoutedEventArgs e ) = > USBInit ( ) ;
private void USBInit ( ) {
2019-09-28 23:51:46 +01:00
mmj = new Paperang ( mmjcx , mmjmd ) ;
2019-09-30 13:51:15 +01:00
mmj . SetLogContext ( logger ) ;
2019-09-28 23:51:46 +01:00
logger . Verbose ( "# printers found: " + mmj . Printer . AvailablePrinters . Count ) ;
if ( ! mmj . Printer . PrinterAvailable ) {
2019-09-30 13:51:15 +01:00
logger . Error ( "Couldn't initialise printer as none is connected" ) ;
2019-09-28 23:51:46 +01:00
return ;
}
2019-08-22 00:55:40 +01:00
logger . Info ( "USB Initialising" ) ;
2019-09-28 23:51:46 +01:00
mmj . Initialise ( ) ;
logger . Debug ( "PrinterInitialised? " + mmj . Printer . PrinterInitialised ) ;
2019-08-29 00:47:44 +01:00
logger . Debug ( "Printer initialised and ready" ) ;
2019-10-02 16:31:46 +01:00
state = AppState . InitDev ;
2019-10-01 21:02:34 +01:00
btInitUSB . IsEnabled = false ;
btDeInitUSB . IsEnabled = true ;
gbOtherFunc . IsEnabled = true ;
gbPrinting . IsEnabled = true ;
2019-08-22 00:55:40 +01:00
}
2019-10-02 16:31:46 +01:00
private void BtDeInitUSB_Click ( object sender , RoutedEventArgs e ) = > USBDeInit ( ) ;
private void USBDeInit ( ) {
logger . Info ( "De-initialising printer" ) ;
2019-10-01 21:02:34 +01:00
mmj . Printer . ClosePrinter ( ) ;
mmj . Printer . Deinitialise ( ) ;
mmj = null ;
2019-10-02 16:31:46 +01:00
state = AppState . UnInitDev ;
try {
gbPrinting . IsEnabled = false ;
gbOtherFunc . IsEnabled = false ;
btInitUSB . IsEnabled = true ;
btDeInitUSB . IsEnabled = false ;
} catch ( Exception ) {
} finally { }
2019-10-01 21:02:34 +01:00
}
2019-10-04 16:59:57 +01:00
private async void BtFeed_Click ( object sender , RoutedEventArgs e ) = > await mmj . FeedAsync ( ( uint ) slFeedTime . Value ) ;
2019-10-04 13:39:41 +01:00
private async void BtPrintText_Click ( object sender , RoutedEventArgs e ) {
2019-10-04 11:18:17 +01:00
if ( ! ( txInput . Text . Length > 0 ) ) {
logger . Warn ( "PrintText event but nothing to print." ) ;
return ;
}
2019-10-04 13:39:41 +01:00
await PrintTextAsync ( txInput . Text , txFont . Text , int . Parse ( txSzF . Text ) ) ;
}
private async Task PrintTextAsync ( string text , string font , int szf ) {
Font fnt = new Font ( font , szf ) ;
2019-10-04 16:59:57 +01:00
TextFormatFlags tf =
TextFormatFlags . Left |
TextFormatFlags . NoPadding |
TextFormatFlags . NoPrefix |
TextFormatFlags . Top |
TextFormatFlags . WordBreak ;
System . Drawing . Size szText = TextRenderer . MeasureText ( text , fnt , new System . Drawing . Size ( mmj . Printer . LineWidth * 8 , 10000 ) , tf ) ;
Bitmap b = new Bitmap ( mmj . Printer . LineWidth * 8 , szText . Height ) ;
Graphics g = Graphics . FromImage ( b ) ;
2019-10-01 17:01:07 +01:00
g . SmoothingMode = System . Drawing . Drawing2D . SmoothingMode . AntiAlias ;
g . TextRenderingHint = System . Drawing . Text . TextRenderingHint . SingleBitPerPixelGridFit ;
g . InterpolationMode = System . Drawing . Drawing2D . InterpolationMode . HighQualityBicubic ;
g . PixelOffsetMode = System . Drawing . Drawing2D . PixelOffsetMode . HighQuality ;
2019-10-04 16:59:57 +01:00
TextRenderer . DrawText ( g , text , fnt , new System . Drawing . Point ( 0 , 0 ) , Color . Black , tf ) ;
2019-10-01 17:01:07 +01:00
g . Flush ( ) ;
2019-10-04 21:14:50 +01:00
await Task . Run ( ( ) = > PrintBitmap ( b , false ) ) ;
2019-10-01 17:01:07 +01:00
g . Dispose ( ) ;
b . Dispose ( ) ;
}
2019-10-04 13:39:41 +01:00
private async void BtPrintImage_Click ( object sender , RoutedEventArgs e ) = > await PrintImageAsync ( ) ;
private async Task PrintImageAsync ( ) {
2019-10-04 11:18:17 +01:00
logger . Debug ( "Invoking file selection dialogue." ) ;
2019-09-10 00:43:58 +01:00
OpenFileDialog r = new OpenFileDialog {
Title = "Select 1 (one) image file" ,
Multiselect = false ,
2019-10-01 21:02:34 +01:00
Filter = "PNG files|*.png|JPEG files|*.jpg;*.jpeg|Jraphics Interchange Format files|*.gif|Bitte-Mappe files|*.bmp|All of the above|*.jpg;*.jpeg;*.png;*.gif;*.bmp" ,
2019-09-10 00:43:58 +01:00
AutoUpgradeEnabled = true
} ;
2019-10-04 11:18:17 +01:00
if ( r . ShowDialog ( ) = = System . Windows . Forms . DialogResult . Cancel ) {
logger . Warn ( "PrintImage event but no file was selected." ) ;
r . Dispose ( ) ;
2019-09-12 00:10:28 +01:00
return ;
}
2019-10-04 13:39:41 +01:00
string fn = r . FileName ;
2019-09-12 00:10:28 +01:00
r . Dispose ( ) ;
2019-10-08 15:03:37 +01:00
//I would love to know how spawning this in another thread via Task.Run((lambda)) and waiting for it to finish via `await`
// is non-blocking, while simply running the code here is blocking.
//someone who is good at threading please help my family is dying
2019-10-04 13:39:41 +01:00
Bitmap bmg = await Task . Run ( ( ) = > {
logger . Debug ( $"Loading image '{fn}' for print" ) ;
Image _ = Image . FromFile ( fn ) ;
logger . Debug ( $"Loaded image '{fn}'" ) ;
logger . Debug ( "Disposed of dialog" ) ;
Bitmap bimg = new Bitmap ( _ , mmj . Printer . LineWidth * 8 ,
2019-10-08 15:03:37 +01:00
( int ) ( mmj . Printer . LineWidth * 8 * ( ( double ) _ . Height / _ . Width ) ) ) ;
2019-10-04 13:39:41 +01:00
logger . Debug ( "Loaded image as Bitmap" ) ;
_ . Dispose ( ) ;
logger . Debug ( "Disposed of Image" ) ;
return bimg ;
} ) ;
await Task . Run ( ( ) = > PrintBitmap ( bmg ) ) ;
bmg . Dispose ( ) ;
2019-10-01 17:01:07 +01:00
}
2019-10-04 21:14:50 +01:00
private async Task PrintBitmap ( Bitmap bimg , bool dither = true ) {
if ( dither ) {
logger . Trace ( "Dithering input bitmap" ) ;
2019-10-08 15:03:37 +01:00
bimg = Grayscale . CommonAlgorithms . Y . Apply ( bimg ) ;
OrderedDithering f = new
OrderedDithering ( bayer4 ) ;
2019-10-04 21:14:50 +01:00
bimg = f . Apply ( bimg ) ;
logger . Debug ( "Dithered Bitmap" ) ;
}
2019-10-01 17:01:07 +01:00
bimg = CopyToBpp ( bimg ) ;
2019-10-01 21:02:34 +01:00
logger . Debug ( "Converted Bitmap to 1-bit" ) ;
int hSzImg = bimg . Height ;
byte [ ] iimg = new byte [ hSzImg * mmj . Printer . LineWidth ] ;
2019-09-10 00:43:58 +01:00
byte [ ] img ;
2019-10-01 17:01:07 +01:00
using ( MemoryStream s = new MemoryStream ( ) ) {
2019-09-10 00:43:58 +01:00
bimg . Save ( s , ImageFormat . Bmp ) ;
2019-10-01 17:01:07 +01:00
img = s . ToArray ( ) ;
2019-09-10 00:43:58 +01:00
}
2019-09-12 00:10:28 +01:00
logger . Debug ( "Got bitmap's bytes" ) ;
2019-10-01 21:02:34 +01:00
bimg . Dispose ( ) ;
logger . Debug ( "Disposed of Bitmap" ) ;
int startoffset = img . Length - ( hSzImg * mmj . Printer . LineWidth ) ;
2019-09-12 00:10:28 +01:00
logger . Debug ( "Processing bytes with offset " + startoffset ) ;
2019-10-01 21:02:34 +01:00
for ( int h = 0 ; h < hSzImg ; h + + ) {
2019-10-01 17:01:07 +01:00
for ( int w = 0 ; w < mmj . Printer . LineWidth ; w + + ) {
2019-10-01 21:02:34 +01:00
iimg [ ( mmj . Printer . LineWidth * ( hSzImg - 1 - h ) ) + ( mmj . Printer . LineWidth - 1 - w ) ] = ( byte ) ~
2019-10-04 11:18:17 +01:00
img [ startoffset + ( mmj . Printer . LineWidth * h ) + ( mmj . Printer . LineWidth - 1 - w ) ] ;
2019-09-09 00:51:36 +01:00
}
2019-09-12 00:10:28 +01:00
}
2019-10-04 11:18:17 +01:00
logger . Debug ( $"Have {img.Length} bytes of print data ({mmj.Printer.LineWidth * 8}x{hSzImg}@1bpp)" ) ;
2019-10-04 16:59:57 +01:00
await mmj . PrintBytesAsync ( iimg , false ) ;
2019-09-10 00:43:58 +01:00
}
2019-10-02 16:31:46 +01:00
#region GDIBitmap1bpp
2019-09-10 00:43:58 +01:00
static Bitmap CopyToBpp ( Bitmap b ) {
int w = b . Width , h = b . Height ;
IntPtr hbm = b . GetHbitmap ( ) ;
BITMAPINFO bmi = new BITMAPINFO {
biSize = 40 ,
biWidth = w ,
biHeight = h ,
biPlanes = 1 ,
biBitCount = 1 ,
biCompression = BI_RGB ,
biSizeImage = ( uint ) ( ( ( w + 7 ) & 0xFFFFFFF8 ) * h / 8 ) ,
biXPelsPerMeter = 1000000 ,
biYPelsPerMeter = 1000000
} ;
2019-10-04 11:18:17 +01:00
bmi . biClrUsed = 2 ;
bmi . biClrImportant = 2 ;
bmi . cols = new uint [ 256 ] ;
bmi . cols [ 0 ] = MAKERGB ( 0 , 0 , 0 ) ;
bmi . cols [ 1 ] = MAKERGB ( 255 , 255 , 255 ) ;
2019-09-30 15:02:12 +01:00
IntPtr hbm0 = CreateDIBSection ( IntPtr . Zero , ref bmi , DIB_RGB_COLORS , out _ , IntPtr . Zero , 0 ) ;
2019-09-10 00:43:58 +01:00
IntPtr sdc = GetDC ( IntPtr . Zero ) ;
2019-09-30 15:02:12 +01:00
IntPtr hdc = CreateCompatibleDC ( sdc ) ;
_ = SelectObject ( hdc , hbm ) ;
IntPtr hdc0 = CreateCompatibleDC ( sdc ) ;
_ = SelectObject ( hdc0 , hbm0 ) ;
_ = BitBlt ( hdc0 , 0 , 0 , w , h , hdc , 0 , 0 , SRCCOPY ) ;
2019-09-10 00:43:58 +01:00
Bitmap b0 = Image . FromHbitmap ( hbm0 ) ;
2019-09-30 15:02:12 +01:00
_ = DeleteDC ( hdc ) ;
_ = DeleteDC ( hdc0 ) ;
_ = ReleaseDC ( IntPtr . Zero , sdc ) ;
_ = DeleteObject ( hbm ) ;
_ = DeleteObject ( hbm0 ) ;
2019-09-10 00:43:58 +01:00
return b0 ;
}
[System.Runtime.InteropServices.DllImport("gdi32.dll")]
public static extern bool DeleteObject ( IntPtr hObject ) ;
[System.Runtime.InteropServices.DllImport("user32.dll")]
public static extern IntPtr GetDC ( IntPtr hwnd ) ;
[System.Runtime.InteropServices.DllImport("gdi32.dll")]
public static extern IntPtr CreateCompatibleDC ( IntPtr hdc ) ;
[System.Runtime.InteropServices.DllImport("user32.dll")]
public static extern int ReleaseDC ( IntPtr hwnd , IntPtr hdc ) ;
[System.Runtime.InteropServices.DllImport("gdi32.dll")]
public static extern int DeleteDC ( IntPtr hdc ) ;
[System.Runtime.InteropServices.DllImport("gdi32.dll")]
public static extern IntPtr SelectObject ( IntPtr hdc , IntPtr hgdiobj ) ;
[System.Runtime.InteropServices.DllImport("gdi32.dll")]
public static extern int BitBlt ( IntPtr hdcDst , int xDst , int yDst , int w , int h , IntPtr hdcSrc , int xSrc , int ySrc , int rop ) ;
static int SRCCOPY = 0x00CC0020 ;
[System.Runtime.InteropServices.DllImport("gdi32.dll")]
static extern IntPtr CreateDIBSection ( IntPtr hdc , ref BITMAPINFO bmi , uint Usage , out IntPtr bits , IntPtr hSection , uint dwOffset ) ;
static uint BI_RGB = 0 ;
static uint DIB_RGB_COLORS = 0 ;
[System.Runtime.InteropServices.StructLayout(System.Runtime.InteropServices.LayoutKind.Sequential)]
public struct BITMAPINFO {
public uint biSize ;
public int biWidth , biHeight ;
public short biPlanes , biBitCount ;
public uint biCompression , biSizeImage ;
public int biXPelsPerMeter , biYPelsPerMeter ;
public uint biClrUsed , biClrImportant ;
[System.Runtime.InteropServices.MarshalAs(System.Runtime.InteropServices.UnmanagedType.ByValArray, SizeConst=256)]
public uint [ ] cols ;
2019-08-27 02:29:43 +01:00
}
2019-10-04 11:18:17 +01:00
static uint MAKERGB ( int r , int g , int b ) = > ( ( uint ) ( b & 255 ) ) | ( ( uint ) ( ( r & 255 ) < < 8 ) ) | ( ( uint ) ( ( g & 255 ) < < 16 ) ) ;
2019-10-02 16:31:46 +01:00
#endregion
2019-08-22 00:55:40 +01:00
}
}