Tek-Tips is the largest IT community on the Internet today!

Members share and learn making Tek-Tips Forums the best source of peer-reviewed technical information on the Internet!

  • Congratulations SkipVought on being selected by the Tek-Tips community for having the most helpful posts in the forums last week. Way to Go!

Open a jpg, mod jpg, close and save jpg 2

Status
Not open for further replies.

cfsjohn

Programmer
Sep 1, 2016
59
0
6
US
I have a Topaz signature pad. It captures the customer's signature at the end of a sale. It can save the signature to a string (it is a very long string), a sig file which I guess would be Topaz proprietary, bmp, jpg, png, tiff and wmf and emf. I have it saving the signatures as an jpg image file. It works. I can print the jpg on the receipt (a VFP9 report). My problem is the signature is very small. I can't find a way via Topaz commands to make the signature larger. The jpg file contains a lot of whitespace aroung the actual signature. If I could somehow (from VFP), open the jpg, find the 1st instance of a black dot in the jpg (that would be where the actual signature starts), maybe I could automatically crop the jpg and resave it ending up with an image that ONLY contains the actual signature (so leaving out all the whitespace around the actual signature). I've looked at using the VFP GDIPlus library and maybe the GDIPlus X library on github (i really don't understand how to download that library from github).

Can anyone tell me the easiest way to get from point a to b on this?

Thanks,
John
 
 https://files.engineering.com/getfile.aspx?folder=af0e6a10-6d0f-4327-bc8a-1069e3407836&file=00598934.jpg
cfsjohn said:
GDIPlus X library on github (i really don't understand how to download that library from github).

Regarding that, the simplest way is to get everything, by clicking code and then Download zip.

save_git_fjqbop.png


There's a further detail to tackle before you can use the download to compile a system.app that works as the gdiplus library. Like many projects the pjx/pjt files are converted using foxbin2prg -
The perhaps simplest way to get the project files back is to simply run the system.pj2 file, it is a PRG. While its header tells its not intended for execution, it works to get the system.pjx necessary to build the system.app from it:

Code:
*--------------------------------------------------------------------------------------------------------------------------------------------------------
* (ES) AUTOGENERADO - ¡¡ATENCIÓN!! - ¡¡NO PENSADO PARA EJECUTAR!! USAR SOLAMENTE PARA INTEGRAR CAMBIOS Y ALMACENAR CON HERRAMIENTAS SCM!!
* (EN) AUTOGENERATED - ATTENTION!! - NOT INTENDED FOR EXECUTION!! USE ONLY FOR MERGING CHANGES AND STORING WITH SCM TOOLS!!
*--------------------------------------------------------------------------------------------------------------------------------------------------------
*< FOXBIN2PRG: Version="1.19" SourceFile="system.pjx" /> (Solo para binarios VFP 9 / Only for VFP 9 binaries)
*
...

The "official" way is to run
Code:
DO FOXBIN2PRG.PRG WITH "<path>\system.pj2"

Well, or actually use source control integration in VFP. It's a topic in itself, though, and I won't even try to explain it here.

When you open the resulting system.pjx in VFP9, use Build and select "Application (app)" in the build options, not the default Win32 executable / COM server (exe).

And then, finally, to use gdiplusx you Do system.app and have it available in _screen.system. Which means you can run code examples. Later you just need to provide the system.app file with your EXE, too.

Chriss
 
The easiest way is using Alexander Golovlev's gpiImage class ( a wrapper for GDI+);
It's available upgraded and documented by Cesar Chalom at

Code:
** using gpiImage 

Set Procedure To gpimage additive

gpinit 	= Createobject('gpInit')
gpimage = Createobject('gpImage')

With gpimage
	.Load(locfile('00598942.jpg'))
	.crop(100,280,1700,300)
        .resize(int(1700/6),int(300/6)) && was too big!
	.saveaspng('test.png')
Endwith

run test.png



Marco Plaza
 
I know this, I used it before GDIPlusX existed, but as far as I know Cesar Chalom then contributed much to GDIPlusX and that makes it the successor of gpimage to me. I don't see how this is easier used than DO system.app, anyway, just my 2c. And there also is a System_Lean.app

Chriss
 
Here, by the way, is a method to crop the image in Y direction and reduce the height.

The idea is to use LockBits, which gives you the raw image data. Assuming the first line of the image is empty, the data is scanned until the first line that differs from top and bottom. To not fail on JPEG artifacts having very light gray pixels within the empty areas, the raw image analized is turned into 1bit per pixel black and white only.

The same idea could then be applied to the image rotated 90 degrees to also determine minx ans maxx, I spared myself to do that.

Code:
Do Locfile("system.app")

Local loBitmap As xfcbitmap
Local loCropped As xfcbitmap
Local loRect As xfcrectangle
Local lnMinY, nMaxY

With _Screen.System.drawing

   loBitmap = .Bitmap.FromFile("00598934.jpg") && adapt with path to file 
   m.lnwidth  = m.loBitmap.Width
   m.lnheight = m.loBitmap.Height

   lnMinY = 0
   lnMaxY = lnheight

   loRect = .rectangle.new(0, 0, m.lnwidth, m.lnheight)
   loBits = loBitmap.LockBits(m.lorect, .Imaging.ImageLockMode.ReadOnly, .Imaging.PixelFormat.Format1bppIndexed)

   lnMinY = 0
   lcEmptyLine = Sys(2600,loBits.Scan0,loBits.Stride)
   lnAdr = loBits.Scan0
   For lnI = 1 To loBits.Height
      lcLine = Sys(2600,lnAdr,loBits.Stride)
      If lcLine==lcEmptyLine
         lnMinY = lnMinY + 1
      Else
         Exit
      Endif
      lnAdr = lnAdr + loBits.Stride
   Endfor lnI

   lnMaxY = loBits.Height-1
   lnAdr = loBits.Scan0+lnMaxY*loBits.Stride
   For lnI = 1 To loBits.Height
      lcLine = Sys(2600,lnAdr,loBits.Stride)
      If lcLine == lcEmptyLine
         lnMaxY = lnMaxY - 1
      Else
         Exit
      Endif
      lnAdr = lnAdr - loBits.Stride
   Endfor lnI

   loBitmap.UnlockBits(loBits)

   lorect = .rectangle.new(0, m.lnMinY, m.lnwidth, m.lnMaxY-m.lnMinY)
   loCropped = m.loBitmap.Clone(m.lorect)
   loCropped.Save("00598934Cropped.jpg") && adapt with path

Endwith

But just to get an impression of what you get from this. IT works fast, too.

cfsjohn said:
reduce the size of the file

JPEG is pretty good at compression, so you will not save bytes by cropping only, you need to play with details of the JPG encoder and its parameters about the JPEG quality and other settings, PNG would perhaps work better. In default settings the cropped image is even larger than with all the whitespace.

Chriss
 
Here is the image.dll function list:
Code:
* Load the DLL functions
* Returns a handle for accessing the bitmap in future operations
DECLARE INTEGER bmp_open_file           IN image.dll STRING cPathname
DECLARE INTEGER bmp_crop_to_content     IN image.dll INTEGER nHandle
DECLARE INTEGER bmp_get_attributes      IN image.dll INTEGER nHandle, STRING@ cWidthOutInches8, STRING@ cHeightOutInches8, STRING@ cBitsPerPixel8, STRING@ cPixelsPerInchX8, STRING@ cPixelsPerInchY8
DECLARE INTEGER bmp_save_file           IN image.dll INTEGER nHandle, STRING@ cNewBmpPathname
DECLARE INTEGER bmp_close               IN image.dll INTEGER nHandle

* Returns a handle for accessing the bitmap in future operations
DECLARE INTEGER bmp_resize              IN image.dll INTEGER nHandle, FLOAT fScaleFactor

I have it all written, but will need to debug it. There are many other functions which can be exposed as well through the API.

--
Rick C. Hodgin
 
Hello Chris, I was referring to the fact that you need to recreate the pjx using foxbin,
then build system.app and redistribute system.app + include a support vcx in your project ,
vs just download a prg based class..

But like you show, of course GDI+X has much more to offer!







Marco Plaza
@nfoxProject
 
Marco plaza said:
+ include a support vcx in your project
I don't see that need. What VCX? All I know is that instead of redistributing system.app you can decide to include all necessary components of GDIPlusX into your own project to not need the System.APP, I don't think that's improving the usage, though. I might be missing something you know better, but so far I never added a VCX to my project.

I find it very simple also with FoxyPreviewer to only need to DO an app. It has become quite a standard, hasn't it? In case of GDIPlusX did you notice you also get intellisense? I don't just mean because of the existance of the _screen.system object after DOing system.app. If you use LOCAL loBitmap as xfcbitmap you get intellisense about the Bitmap GDI+ class when typing loBitmap, not only when typing _screen.System.Drawing.Bitmap.

Chriss

PS: Providing an already built system.app would indeed be nice. I know they had that when codeplex still existed and the project was there. There's nothing hindering to provide the single download on GitHub, too. I don't know why it's not provided, it wouldn't even have a VFP runtime dependency unless there is code only working in VFP9.
 
Indeed GDIX is a fantastic project.. About the vcx.. the page says under distribution files: "GDIPlusX.vcx - This visual class library contains the imgCanvas class and will need to be compiled into your application for distribution, if you utilize this class." - I see it's not needed here so ok.. just skip it.

I'll fork the project and make a pull request to include a bin folder with system.app + system_lean.app for vfp9.










Marco Plaza
@nfoxProject
 
To add control about the image quality (lower quality = higher compression rate) I added:

Code:
   Local myencoderparameter As xfcencoderparameter
   Local myencoderparameters As xfcencoderparameters
   myencoderparameters = .imaging.encoderparameters.new(1)

   myencoderparameter = .imaging.encoderparameter.new(.imaging.encoder.quality, 10)
   myencoderparameters.param.add(myencoderparameter)
   loCropped.Save("00598934Cropped.jpg", .imaging.imageformat.jpeg, myencoderparameters)

And it turned out that the cropped image only gets smaller than your original pad JPG if using a very low 10% quality. And the quality of the signature drastically degrades with that setting.

The only benefit would be that you could adapt the report layout, though sending the image to the back already solved that problem. And actually, by having differtly sized cropped images, you would not get same results, even if you manage to set stretching of the images without aspect ratio distortion.

So, in very short: I doubt you will save disk space by cropping the images and keep a sensible image quality at the same time. Your pad already does a great job in choosing the jpg compression parameters to create small files. And just think of the different lengths of names: The cropped signature images just pose a new problem of where and how large to position an image control in your report layout to print the cropped signatures with varying widths. It's an advantage the pad images are all the same resolution of 2000x700 pixels.

Let's see how Rick manages with his DLL, maybe he has a better way than what I managed to get out of gdiplusx.

Chriss
 
Marco Plaza said:
I'll fork the project and make a pull request to include a bin folder with system.app + system_lean.app for vfp9

Thank you.

Chriss
 
Last not least, I compared what an image software like Gimp manages to do.

The result of Gimp saving its perfectly cropped image ("crop to content") with same quality settings as it finds in the original JPG is a file with 87% size. So it performs better than GDI+ encoders. But I don't think you get a much better result with anything else.

With Ricks' DLL you'll only need a few lines of code to save about 10% disk space - not bad but surely less than you expected.

What you could do with the cropped signatures is keep your current report design with the full width image sent to back, but draw this cropped signature centered on a white 2000x700 pixel image you create on the fly. That at least centers all signatures, no matter where they were originally positioned, that's a slight layout advantage I would suggest.

Recomposing the image you can also decide to position the signature into the right bottom quarter, whatever, but you can be sure it has enough room as it came from 2000x700 pixels, originally. And in saved with the data it uses up perhaps 10% less space.

Still quite some effort just for that.

Chriss
 
Something to consider is that the signature doesn't have to be perfect. It's displaying on a receipt printer, so it can be a little pixelated. If you drop the resolution down to 100dpi, and you're printing a receipt printer, you'll only need maybe 400 pixels max, probably more like 300 horizontally, and then scaled down proportionately. That would greatly save on space even if you kept it in a PNG lossless format.

There's also a new lossless format (called QOI) that has gotten good compression on certain types of files. It's a very simple algorithm, which is why it's gotten some attention. If there were a strong contrast between the white background and the actual signature portion it would compress nicely in that format due to its encoding scheme.

Keeping everything in its raw format isn't necessary in my thinking.

I wrote the crop to content algorithm last night, and have had the scale algorithm since the 00s. If there are other / better algorithms they can be implemented. Plus, the source code will be there to tweak as needed. The crop to content is looking for the first non-white pixels to find the content's inner edge, but I think something like GIMP's "Threshold" value would be more desirable, so something like this would allow you to pass in 15.0 which would allow some pixelation / dithering on the image to be ignored, and crop down to decent color contrasts. Colors of RGB(255,255,255) and RGB(254,255,255), etc. would be ignored in those cases as "white background" and ignored. Another would be to either sample the outer bands and derive the background color, or to pass in a reference color to use that as the background.

Code:
DECLARE INTEGER bmp_crop_to_content IN image.dll INTEGER nHandle, ;
FLOAT fThreshold   && Add this parameter to make it work with dithered coloring, ;
INTEGER nRgb       && Add this parameter to use a known background color, ;
INTEGER lAutoRgb   && Add this parameter to pass in non-0 to have it auto-detect the background color

I almost wrote a GIMP competing product at one point. I had tried to work with the GIMP developers to add some features. They were very receptive at first, but as they researched about me online they mocked me for my faith. They stopped answering my questions and responding to my posts, so I moved on. I had wanted to create a non-committal operator stack that allowed you to go back to some operation in the history of operations on the image, tweak the adjustments and have it filter through and affect all later applied operations. I also wanted to create a new type of image format I called floan, which related to the original pixel dimensions in the source bitmap, processed down to the target. But rather than losing bits in scaling it would retain its original bit density, and simply apply the floans onto the projected image. I would've called it Wonder. It would've done all the things I use GIMP for plus several more. I would've added a programming language to allow image objects to be created and operated on.

If only there were time.

--
Rick C. Hodgin
 
Yes, other formats than JPG were on my mind already, too. I mentioned PNG. But how I know compression in PNG from GIMP, there are only 9 compression levels and PNGs got quite large in my test, astonishingly, as they are the successor of GIF which already was best for images with a small palette of colors, i.e. graphical images like these signature scribbles.

You're right that the image quality isn't important, I just also saw thermo printers better at greyscales. You're right about the dpi, I didn't think about that. So you could even reduce the density.

The threshold becomes less necessary if you do as I did with Lockbits and use the image converted to 1bpp pure black/white, that automatically makes very light gray pixels white. To find the content that's the easiest way, also less bytes to scan through. And you could also only scan every second line, you don't have to ccrop perfectly. But even within VFP using SYS(2600) it's fast, so there are no performance issues.

Otherwise, I know, you get more ideas than you have time to follow up all of them, that's one of the unsolvable problems. And then you get more ideas by starting with some of them.

Chriss
 
I don't know if this helps but I've needed to display image files on a number of occasions when developing software.

I store the path and filename of the image in a table text field. When I want to view it I make this call:

llRetVal=(WinShellExecute(lcFileName)>31)

That executes the following function that invokes whatever application is setup in Windows to display the image file:

FUNCTION WinShellExecute
LPARAMETER tcFileName,tcWorkingDirectory,tcOperation,tcParameters
LOCAL lnRetVal,lcFileName,lcWorkingDirectory,lcOperation,lcParameters
DECLARE INTEGER ShellExecute IN SHELL32.DLL ;
INTEGER nWinHandle,;
STRING cOperation,;
STRING cFileName,;
STRING cParameters,;
STRING cDirectory,;
INTEGER nShowWindow
lcFileName=ALLTRIM(tcFileName)
lcOperation=IIF(VARTYPE(tcOperation)<>"C","Open",ALLTRIM(tcOperation))
lcParameters=IIF(VARTYPE(tcParameters)<>"C","",ALLTRIM(tcParameters))
lcWorkingDirectory=IIF(VARTYPE(tcWorkingDirectory)<>"C","",ALLTRIM(tcWorkingDirectory))
lnRetVal=ShellExecute(0,lcOperation,lcFileName,lcParameters,lcWorkingDirectory,1)
RETURN (lnRetVal)

Once you have the image on the screen you can use the application's own functions to zoom in to the image.

It's pretty old program code but it still works.

Regards
Gary
 
I continued experimenting. My idea to also determine MinX and MaxX after rotating turned out to be harder than I thought. Because, after Bitmap.RotateFlip() the LockBits method doesn't work, I get memory access violations. But I managed to make it work, when first establishing a square image extended to 2000x2000 pixels.

And then, finally I get a 4KB GIF out of GDIPlusX. Opening the result GIF image with Gimp shows that the GDI+ gif encoder still uses an oversized 216 color palette, though, and it can be reduced to a 1bit per pixel monochrome GIF with only 2.5 KB. PNG slightly larger, astonishingly. I'm speaking of the sample "Hook" signature you attached, cfsjohn. Original file size was 13KB.

There are ways to create color palettes, it's not that straight forward, though. I found an example that also describes what's so complicated about it here: I didn't apply this, but it all gets quite messy, lengthy code, if I add this to my crop rectangle determination code. And though any lenghty code can be put into a function to finally only become one line, I look forward to Rick's DLL now.

Chriss

PS: For comparison: By just changing the pixel format to monochrome GIMP also manages to turn the JPEG uncropped into a 5 KB GIF and 4 KB PNG, respectively. Which means the main savings are already gained from converting to purely monochrome (not 256 tones grayscale, just black/white).
 
I have it all coded, but I haven't had a chance to debug it. Will do so hopefully tomorrow.

--
Rick C. Hodgin
 
I apologize. It'll be this weekend before I can get it debugged. Life things.

--
Rick C. Hodgin
 
Attached is my first attempt at it. It has some example code. It should be relatively straight-forward to expand. Please report any issues.

See the full source code on GitHub. It's a Visual Studio 2003 project, so you'll have to upgrade it possibly through a few steps. I developed it on a Windows 2000 Professional virtual machine.

Code:
LOCAL img AS ImageBitmap

img = CREATEOBJECT("ImageBitmap")
img.open("trig_series.png")   && Uses GDI+, so anything the machine supports can be loaded and saved
img.crop()                    && Will crop to the first non-white character, see other parameters for options
img.get_attributes()

* See the actual image.prg source code.
* There are several properties that are populated with img.get_attributes().
* These can be used to determine how much to resize an image, for example

* Resize to 80% of its current size
img.resize(0.80)
img.save("trig_series_resized.jpg")
img.save("trig_series_resized.png")

* The image remains in memory until explicitly .close()'d, even if img goes out of scope.
img.close()

--
Rick C. Hodgin
 
 https://files.engineering.com/getfile.aspx?folder=0487dcc3-5f27-4a7d-a6be-f84831a0590b&file=image.zip
1st error I see is in your VFP code, the open method does not open tcImagePathname but hardcoded "_trig_series.png"

I don't get crop to actually crop the image, neither the _trig_series.png is cropped, there still is white border in the resized images, nor does it crop the hook signature cfsjohn attached as 00598934.jpg (reattached here).

I used nrgb=RGB(253,253,253)=16645629 as tnRGB parameter (that is the background color the jpg found out with GIMP. And 2 as tnType (use tnRGB). I also tried lower and higher values as I assume that's a threshold, not the exact color. The jpg of cfsjohns device isn't using exact white and it has JPEG artifacts, but crop should nevertheless cut off most of that image and instead does not crop at all.

Chriss
 
 https://files.engineering.com/getfile.aspx?folder=3af176c8-8a93-4342-813a-340101342c2f&file=00598934.jpg
Chris Miller said:
open method does not open tcImagePathname but hardcoded "_trig_series.png"

True. Corrected. I originally had it as a top-down procedural program and refactored it into the class. I missed some things. I apologize. This past week has been difficult.

Chris Miller said:
I don't get crop to actually crop the image

True. Corrected. The returned lnResult was the new this.nHandle that needed to be set. In my procedural code I used it, but in the class refactoring I forgot it.

Chris Miller said:
The jpg of cfsjohns device isn't using exact white and it has JPEG artifacts, but crop should nevertheless cut off most of that image and instead does not crop at all.

I didn't try it on cfsjohns image. I'll try that now. Will post the code updates here in a bit. I need to add another parameter to specify the threshold for the indicated color.

--
Rick C. Hodgin
 
Status
Not open for further replies.

Part and Inventory Search

Sponsor

Back
Top