at3s 4 Posted February 22, 2021 Actually I'm looking for a function which could convert TBitmap to TBytes for printing in ESC/POS Thermal Printer. I'm trying to make working a code from here, but also got an effect as on the author picture - white lines and not correct text on an image as well. Has someone a working code? Share this post Link to post
Remy Lebeau 1393 Posted February 23, 2021 22 hours ago, at3s said: Actually I'm looking for a function which could convert TBitmap to TBytes It really depends on the format that the ESC/POS is expecting. It might be as simply as using TBitmap.SaveToStream() to save to a TBytesStream, and then using the stream's Bytes and Size properties. Or it may be more complex, like having to extract the individual pixels, like that other code is doing. You are going to have to provide more details about your particular situation. Share this post Link to post
at3s 4 Posted February 24, 2021 14 hours ago, Remy Lebeau said: It really depends on the format that the ESC/POS is expecting. It might be as simply as using TBitmap.SaveToStream() to save to a TBytesStream, and then using the stream's Bytes and Size properties. Or it may be more complex, like having to extract the individual pixels, like that other code is doing. You are going to have to provide more details about your particular situation. Here is my code. It's mostly the same as is in the sources here or here. And I'm getting the same result with the horizontal blank lines as here. uses System , System.SysUtils , System.UITypes , FMX.Graphics ; type TRgbTriple = packed record // do not change the order of the fields, do not add any fields Blue: Byte; Green: Byte; Red: Byte; end; // --------------------------- procedure TThermalPrinter.SendString(AValue: String); var SND: TBytes; begin SND := TEncoding.ASCII.GetBytes(AValue); LSockect.SendData(SND); // send data to the printer end; procedure TThermalPrinter.DoSetLineSpacing(AValue : integer); begin if (AValue = 0) then begin SendString(#$1B#$32{, false}); //Resetset to default end else begin SendString(#$1B#$33 + AnsiChar(AValue){, false}); end; end; procedure TThermalPrinter.DoPrintBitmap(const ABitmap : TBitmap; const BitsPerSlice : Byte); const Threshhold = 127; type TBitArray = array of boolean; TRGBTripleArray = ARRAY[Word] of TRGBTriple; pRGBTripleArray = ^TRGBTripleArray; // Use a PByteArray for pf8bit color. var BMPData: TBitmapData; vCol : integer; vRow : integer; vIndex : integer; vSliceIndex : integer; vBytePos : integer; vBitPos : integer; vOffset : integer; vLuminance : integer; vLine: pRGBTripleArray; vPixel: TAlphaColor; vDots: TBitArray; vSlice : Byte; vBit : Byte; vTmpBit: Byte; vVal: boolean; vTempStr : string; begin if not Assigned(ABitmap) then exit; ABitmap.Map(TMapAccess.Read, BMPData); try SetLength(vDots, (ABitmap.Height * ABitmap.Width)); vIndex := 0; for vRow := 0 to ABitmap.Height-1 do begin for vCol := 0 to ABitmap.Width-1 do begin vPixel := BMPData.GetPixel(vCol, vRow); vLuminance := Trunc((TAlphaColorRec(vPixel).R * 0.3) + (TAlphaColorRec(vPixel).G * 0.59) + (TAlphaColorRec(vPixel).B * 0.11)); vDots[vIndex] := (vLuminance < 127); Inc(vIndex); end; end; DoSetLineSpacing(24); SendString(' '); vOffset := 0; while (vOffset < ABitmap.Height) do begin SendString(#$1B'*'#33 + AnsiChar(Lo(ABitmap.Width)) + AnsiChar(Hi(ABitmap.Width)){, false}); vTempStr := ''; for vCol := 0 to ABitmap.Width-1 do begin for vSliceIndex := 0 to 2 do begin // Remember, 24 dots = 24 bits = 3 bytes. vSlice := 0; for vBit := 0 to 7 do begin vBytePos := (((vOffset div 8) + vSliceIndex) * 8) + vBit; vBitPos := (vBytePos * ABitmap.Width) + vCol; vVal := false; if (vBitPos < Length(vDots)) then begin vVal := vDots[vBitPos]; end; if vVal then vTmpBit := 1 else vTmpBit := 0; vSlice := vSlice or (vTmpBit shl (7 - vBit)); end; vTempStr := vTempStr + AnsiChar(vSlice); end; end; Inc(vOffset, 24); SendString(vTempStr + #13#10); end; DoSetLineSpacing(0); SendString(' '); finally vDots := nil; ABitmap.Unmap(BMPData); end; end; Share this post Link to post
stijnsanders 35 Posted March 4, 2021 If I recall correctly, the GetPixel method is rather slow. Does FMX.Graphics' TBitmap also have a Scanline pointer property? Share this post Link to post
Remy Lebeau 1393 Posted March 4, 2021 (edited) 13 hours ago, stijnsanders said: Does FMX.Graphics' TBitmap also have a Scanline pointer property? FMX's TBitmap has a Map() method for accessing the raw pixel data. Map() returns a TBitmapData, which has a GetScanline() method. Edited March 4, 2021 by Remy Lebeau Share this post Link to post
Cristian Peța 103 Posted March 4, 2021 On 2/24/2021 at 9:25 AM, at3s said: Here is my code. It's mostly the same as is in the sources here or here. And I'm getting the same result with the horizontal blank lines as here. You are using examples from others that doesn't work or doesn't work in your case. Maybe it's time to read the documentation of that printer, understand it and do it right. From what I understood reading a little Nicholas Piasecki's article is that you need to send binary data. Not ASCII. And from your code it's clear that you need (like in Nicholas Piasecki's article) a monochrome, one bit per pixel image. In top-bottom and right-left order. This is binary data. A sort of simplified PCX graphic format (long time ago I wrote a little procedure to save TBitmap to PCX format). Your don't need SendString(AValue: String) function. Use directly LSockect.SendData() instead. And don't use String but TBytes like LSockect.SendData() is expecting. Share this post Link to post
David Heffernan 2345 Posted March 4, 2021 Do you have a specification for what these s printer expects to receive? Share this post Link to post
at3s 4 Posted March 5, 2021 15 hours ago, Cristian Peța said: You are using examples from others that doesn't work or doesn't work in your case. Maybe it's time to read the documentation of that printer, understand it and do it right. From what I understood reading a little Nicholas Piasecki's article is that you need to send binary data. Not ASCII. And from your code it's clear that you need (like in Nicholas Piasecki's article) a monochrome, one bit per pixel image. In top-bottom and right-left order. This is binary data. A sort of simplified PCX graphic format (long time ago I wrote a little procedure to save TBitmap to PCX format). Your don't need SendString(AValue: String) function. Use directly LSockect.SendData() instead. And don't use String but TBytes like LSockect.SendData() is expecting. Actually Delphi code above should do exactly what Nicholas Piasecki did in his article using C#. Can you please send me in PM your procedure of saving TBitmap to PCX format? Share this post Link to post
at3s 4 Posted March 5, 2021 14 hours ago, David Heffernan said: Do you have a specification for what these s printer expects to receive? I used a lot of specification for ESC\POS printer. Actually I tried to make my code working in RawBT application (something like virtual ESC\POS printer) and now I'm trying to use GOOJPRT PT-210 device. I did not find a specification for this model of printer. but I require more general solution to be able to print a receipt from my Android application. Share this post Link to post
David Heffernan 2345 Posted March 5, 2021 Don't these printers all require different input? Or am I missing something? Share this post Link to post
Cristian Peța 103 Posted March 5, 2021 11 minutes ago, at3s said: Actually Delphi code above should do exactly what Nicholas Piasecki did in his article using C#. If you don't have the documentation then at least follow what that article says clearly: " This is because low-level programmers, such as those who designed the ESC/POS language, tend to blur the lines between data types: it’s all bytes at the end of the day." You need to write binary data. Simply don't put binary data in String and convert using TEncoding.ASCII.GetBytes(). 18 minutes ago, at3s said: Can you please send me in PM your procedure of saving TBitmap to PCX format? This won't help you. But if you want to know I used TBytes. Share this post Link to post
at3s 4 Posted March 5, 2021 7 hours ago, Cristian Peța said: If you don't have the documentation then at least follow what that article says clearly: " This is because low-level programmers, such as those who designed the ESC/POS language, tend to blur the lines between data types: it’s all bytes at the end of the day." You need to write binary data. Simply don't put binary data in String and convert using TEncoding.ASCII.GetBytes(). This won't help you. But if you want to know I used TBytes. As you can see in my sample, I don't send a string to the POS printer but rather TBytes. Probably a string type of AValue parameter of SendString procedure twice confusing you. Sure, it AValue is not properly populated, SendString procedure shall send something weird. But I got an image as output on printer, but it's just cutting by horizontal lines. And this Lazarus implementation of Nicholas Piasecki's code doesn't work for me as well. Share this post Link to post
Cristian Peța 103 Posted March 5, 2021 (edited) Here you are sending String converted with TEncoding.ASCII.GetBytes() to TBytes. On 2/24/2021 at 9:25 AM, at3s said: SendString(#$1B'*'#33 + AnsiChar(Lo(ABitmap.Width)) + AnsiChar(Hi(ABitmap.Width)){, false}); Why not directly? LSockect.SendData([$1B, 42, 33, Lo(ABitmap.Width), Hi(ABitmap.Width)]); Edited March 5, 2021 by Cristian Peța 1 Share this post Link to post
at3s 4 Posted March 5, 2021 8 minutes ago, Cristian Peța said: Here you are sending String converted with TEncoding.ASCII.GetBytes() to TBytes. Why not directly? LSockect.SendData([$1B] + [42] + [33] + [Lo(ABitmap.Width)] + [Hi(ABitmap.Width)]); You're awesome! You've fixed my problem. Thanks a lot. Share this post Link to post
Cristian Peța 103 Posted March 5, 2021 (edited) If this was the fix then this was a lottery. I was thinking you need to get rid of that SendString() everywhere. At least when you send binary data. And Lo(ABitmap.Width) is binary data. If you want to send text and to be readable in source you can use AnsiString and convert to TBytes. Using String to store bytes is as @David Heffernan would say: perverse. Edited March 5, 2021 by Cristian Peța Share this post Link to post
Cristian Peța 103 Posted March 6, 2021 To send text you could use this function. If the previous change fixed the issue I suppose this will also fix it without to change anything else. You could also store binary data in AnsiString but I would not do this. It's better to use TBytes for this. procedure TThermalPrinter.SendString(AValue: AnsiString); var SND: TBytes; begin SetLength(SND, Length(AValue)); CopyMemory(@SND[0], @AValue[1], Length(AValue)); LSockect.SendData(SND); // send data to the printer end; And better this in TThermalPrinter.DoPrintBitmap() vTempStr : AnsiString; 1 Share this post Link to post
jon eskandi 0 Posted February 24, 2022 (edited) i've try your code and face the same problem. a white line printed for each loop. could you show the working code ? thank you Edited February 25, 2022 by jon eskandi Share this post Link to post
jon eskandi 0 Posted February 26, 2022 On 2/25/2022 at 12:34 AM, jon eskandi said: i've try your code and face the same problem. a white line printed for each loop. could you show the working code ? thank you finally i found the problem is like Cristian Peta said before. just use byte SND := TEncoding.ASCII.GetBytes(AValue); should be SND := bytesof(AValue); that's it. thank you all Share this post Link to post
Remy Lebeau 1393 Posted February 28, 2022 On 2/25/2022 at 5:56 PM, jon eskandi said: SND := TEncoding.ASCII.GetBytes(AValue); should be SND := bytesof(AValue); BytesOf() for a (Wide|Unicode)String is just a wrapper for TEncoding.Default.GetBytes(), eg: SND := TEncoding.Default.GetBytes(AValue); Note that TEncoding.Default is TEncoding.ANSI on Windows, but is TEncoding.UTF8 on other platforms. Share this post Link to post
jon eskandi 0 Posted March 11, 2022 it is true Remy Lebeau if we use TEncoding.ASCII will result strange character on some platform. so probably best we use "Default". good point Remy thank you. Share this post Link to post