FUNCTION Bmp2ICO(sFile, aFilesName) EXTERNAL ARRAY aFilesName * sFile - file name for resulting .ico file * aFilesName - array with the list of .bmp files LOCAL nFiles, i, nf, fn m.nFiles = ALEN(m.aFilesName, 1) LOCAL ARRAY aFiles(nFiles) LOCAL sLine, nOffset, nWidth, nHeight, nBit0, nShift FOR m.i = 1 TO m.nFiles m.nf = FOPEN(m.aFilesName(m.i)) IF m.nf < 1 = WMsg("!", "Cannot open file " + m.aFilesName(m.i) + " !") RETURN .F. ENDIF m.aFiles(m.i) = FREAD(m.nf, 999999) = FCLOSE(m.nf) ENDFOR m.fn = FULLPATH(m.sFile) + IIF(EMPTY(JUSTEXT(m.sFile)), ".ICO", "") m.nf = FCREATE(m.fn) IF m.nf < 1 = WMsg("!", "Cannot create file " + m.fn + " !") RETURN .F. ENDIF m.sLine = "" m.sLine = m.sLine + CHRN( 0, 2) && 0 reserved m.sLine = m.sLine + CHRN( 1, 2) && 2 type m.sLine = m.sLine + CHRN(m.nFiles, 2) && 4 Number of Icons in this file m.nOffset = LEN(m.sLine) + 16 * m.nFiles FOR m.i = 1 TO m.nFiles m.nWidth = ASC(SUBSTR(m.aFiles(m.i), 19, 1)) &&width of the image, in pixels m.nHeight = ASC(SUBSTR(m.aFiles(m.i), 23, 1)) &&height of the image, in pixels m.nBit0 = ASC(SUBSTR(m.aFiles(m.i), 29, 1)) &&Bits per pixel m.sLine = m.sLine + CHR(m.nWidth) && 0 width of the image, in pixels m.sLine = m.sLine + CHR(m.nHeight) && 1 height of the image, in pixels (OR & AND bitmaps) m.sLine = m.sLine + SUBSTR(m.aFiles(m.i), 47, 1) && 2 Number of Colors m.sLine = m.sLine + CHR(0) && 3 reserved m.sLine = m.sLine + SUBSTR(m.aFiles(m.i), 27, 2) && 4 Number of Planes m.sLine = m.sLine + SUBSTR(m.aFiles(m.i), 29, 2) && 6 Bits per pixel m.nShift = ASCN(SUBSTR(m.aFiles(m.i), 11, 4)) &&offset from the beginning of the file to the bitmap data m.aFiles(m.i) = SUBSTR(m.aFiles(m.i), 15, 40 + IIF(m.nBit0 > 8, 0, 4 * 2^m.nBit0)) + SUBSTR(m.aFiles(m.i), m.nShift+1) &&image m.aFiles(m.i) = STUFF(m.aFiles(m.i), 9, 1, CHR(m.nHeight * 2)) &&height of the image, in pixels (OR & AND bitmaps) m.nWidth = CEILING(m.nWidth / 8) && meaning bytes in a row for AND bitmap m.nWidth = 4 * CEILING(m.nWidth / 4) &&bytes in a row for AND bitmap m.aFiles(m.i) = m.aFiles[m.i] + REPLICATE(CHR(0), m.nWidth * m.nHeight) &&AND bitmap m.sLine = m.sLine + CHRN(LEN(m.aFiles(m.i)), 4) && 8 Size of image area m.sLine = m.sLine + CHRN(m.nOffset, 4) &&12 offset to image area m.nOffset = m.nOffset + LEN(m.aFiles(m.i)) ENDFOR = FWRITE(m.nf, m.sLine) FOR m.i = 1 TO m.nFiles = FWRITE(m.nf, m.aFiles(m.i)) ENDFOR = FCLOSE(m.nf) RETURN .T. *------------------------------- FUNCTION ASCN(s) &&converts binary string to numeric * s - string, bytes go from tail to head LOCAL i, n m.n = 0 FOR m.i = LEN(m.s) TO 1 STEP - 1 m.n = m.n * 256 + ASC(SUBSTR(m.s, m.i, 1)) ENDFOR RETURN m.n *------------------------------- FUNCTION CHRN(n, ln) &&converts numeric value to binary string, &&bytes go from tail to head * n, ln - numeric value and output string length LOCAL i, s, sc m.s = "" m.sc = m.n FOR m.i = 1 TO m.ln m.s = m.s + CHR(m.sc % 256) m.sc = INT(m.sc / 256) ENDFOR RETURN m.s
VFP Visual FoxPro and Images, graphics, GdiPlus, GdiPlusX, FoxCharts, FoxPaint, charting, tips and tricks, code samples, VFPX, FoxyPreviewer etc...
2014-03-12
Convert BMP to ICO
The code below is a courtesy of Sergey Karimov, and converts BMP files to the ICO format, keeping the same appearance, and quality.
2012-08-17
FoxyPreviewer latest changes in v2.99
IMPORTANT FIX
I think I finally caught one of the most challenging bugs we had in FoxyPreviewer, that made the reports render only the data from the first field in some reports that used Private DataSessions, and some other specific settings.
I received complaints from 4 people in the last year, but nobody could give me any clue about what was going on, or to help me to reproduce the problem. I finally found a good soul, Flavio Cardoso, from the brazilian VFP user group, who had the same issue, and provided the needed information, and worked with me during some hours to solve this JUMBO issue.
So, please, test the latest version with care v2.99m or higher, and let me know how it goes!
Below, some information about the latest changes in FoxyPreviewer, in case you missed:
v2.99m - 2012-08-17
** VERY IMPORTANT FIX **In some cases, when using Private datasessions, depending on the environment settings
some reports rendered the data of the 1st field only. If you had this issue before, please test this release
Other fixes / improvements
Fix: FoxyPreviewer disabled form's close button - http://foxypreviewer.codeplex.com/workitem/9275
Fix: Rearranged the order of the buttons in the preview toolbar and context menu, in order to make the buttons to appear at the same order in both places, thanks to Mark Winston
New properties:
- “nThermFormWidth” – numeric, specifies the width of the Thermometer form. Useful when a big caption is needed. Works only if “.nThermType = 2 && Windows default”
- “lShowFileFormatIcons” - logical, allows hiding the file format icons in the context menus.
Cool information:
Mark Winston, brought to my knowledge that "sec(s) or secs" is not the correct abreviation for the word "seconds". After a small research, we found that just an "s" is the correct way to abreviate. I will not change the default behavior, because it's the way VFP originally used in the Thermometer form, that shows the report progress. If you want to change it, it's very easy, just try:
BTW, You can play changing these properties below as well, if you prefer:
v2.99c - 2012-08-08
- Fix in PDF listener, adjusting some constant values, in order to store metadata correctly.
http://foxypreviewer.codeplex.com/workitem/9434 - Thanks to Martin SYkora
- Fix in RTF Listener, now making the default language according to the codepage selected (originally was always forcing to Russian)
- Fix for issue: http://foxypreviewer.codeplex.com/workitem/9506
- New property: “cFaxPRG”, allowing sending faxes from FoxyPreviewer using your custom Faxing procedures. Notice that FOxyPreviewer does not bring any internal code to manage faxes. With this property, I'm just opening a "door" to allow you to receive some info from FoxyPreviewer and send your documents using your Faxing app.
To make Foxy show a "Send to fax" button in the email form, you need to fill the property “cFAXprg”, with the name of a PRG that will be responsible of sending the fax, for example:
Then, your “MYSENDFAX.PRG” program MUST start with a LPARAMETERS statement, that will receive from FoxyPreviewer some needed parameters to send your fax:
v2.99 - 2012-06-20
- Included "lSilent" property in simplified mode
- Fix in PDF listener, allowing images embedded in the EXE to be shown in the exported files
- Fix in PDF and RTF listener, allowing merging reports using OBJECTTYPE 10 (Other types not available yet)
- Included new PRG: "FOXYGETIMAGE.PRG". Include this file in your EXE project if you have images embedded in your EXE that you want to appear in your report. This may bring some security issues to your EXE, because this program will allow FoxyPreviewer to access the embedded images of your EXE.
- Fixed Issue "Emailto, subject and file name settings do not update" http://foxypreviewer.codeplex.com/workitem/9362
- Fix in ExcelListener: to allow flexible Currency and ()'s for Negative - by Rick Bean
- Fix in Report exporting, when some objects were not respecting the original report position (front and back) http://foxypreviewer.codeplex.com/workitem/9276
FoxyPreviewer Links
I think I finally caught one of the most challenging bugs we had in FoxyPreviewer, that made the reports render only the data from the first field in some reports that used Private DataSessions, and some other specific settings.
I received complaints from 4 people in the last year, but nobody could give me any clue about what was going on, or to help me to reproduce the problem. I finally found a good soul, Flavio Cardoso, from the brazilian VFP user group, who had the same issue, and provided the needed information, and worked with me during some hours to solve this JUMBO issue.
So, please, test the latest version with care v2.99m or higher, and let me know how it goes!
Below, some information about the latest changes in FoxyPreviewer, in case you missed:
v2.99m - 2012-08-17
** VERY IMPORTANT FIX **In some cases, when using Private datasessions, depending on the environment settings
some reports rendered the data of the 1st field only. If you had this issue before, please test this release
Other fixes / improvements
Fix: FoxyPreviewer disabled form's close button - http://foxypreviewer.codeplex.com/workitem/9275
Fix: Rearranged the order of the buttons in the preview toolbar and context menu, in order to make the buttons to appear at the same order in both places, thanks to Mark Winston
New properties:
- “nThermFormWidth” – numeric, specifies the width of the Thermometer form. Useful when a big caption is needed. Works only if “.nThermType = 2 && Windows default”
- “lShowFileFormatIcons” - logical, allows hiding the file format icons in the context menus.
Cool information:
Mark Winston, brought to my knowledge that "sec(s) or secs" is not the correct abreviation for the word "seconds". After a small research, we found that just an "s" is the correct way to abreviate. I will not change the default behavior, because it's the way VFP originally used in the Thermometer form, that shows the report progress. If you want to change it, it's very easy, just try:
DO FoxyPreviewer.App _Screen.oFoxyPreviewer._SecondsText = "s by Mark Winston" REPORT FORM LOCFILE(_Samples + "\Solution\Reports\Wrapping.frx") PREVIEW |
BTW, You can play changing these properties below as well, if you prefer:
? _Screen.oFoxyPreviewer._CancelinStrText ? _Screen.oFoxyPreviewer._AttentionText ? _Screen.oFoxyPreviewer._CancelQueryText ? _Screen.oFoxyPreviewer._InitStatusText ? _Screen.oFoxyPreviewer._PrepassStatusText ? _Screen.oFoxyPreviewer._SecondsText |
v2.99c - 2012-08-08
- Fix in PDF listener, adjusting some constant values, in order to store metadata correctly.
http://foxypreviewer.codeplex.com/workitem/9434 - Thanks to Martin SYkora
- Fix in RTF Listener, now making the default language according to the codepage selected (originally was always forcing to Russian)
- Fix for issue: http://foxypreviewer.codeplex.com/workitem/9506
- New property: “cFaxPRG”, allowing sending faxes from FoxyPreviewer using your custom Faxing procedures. Notice that FOxyPreviewer does not bring any internal code to manage faxes. With this property, I'm just opening a "door" to allow you to receive some info from FoxyPreviewer and send your documents using your Faxing app.
To make Foxy show a "Send to fax" button in the email form, you need to fill the property “cFAXprg”, with the name of a PRG that will be responsible of sending the fax, for example:
_Screen.oFoxyPreviewer.cFaxPrg = “mySendFax.prg” |
Then, your “MYSENDFAX.PRG” program MUST start with a LPARAMETERS statement, that will receive from FoxyPreviewer some needed parameters to send your fax:
LPARAMETERS tcFile, tcFaxNumber, tcHTMLBody, tcSubject* Where: * tcFile = the file name that brings your report * tcFaxNumber = the fax number that your client will fill in the email field * in the email form * tcHTMLBody = the text that your client wrote in the email form * tcSubject = the subject filled |
v2.99 - 2012-06-20
- Included "lSilent" property in simplified mode
- Fix in PDF listener, allowing images embedded in the EXE to be shown in the exported files
- Fix in PDF and RTF listener, allowing merging reports using OBJECTTYPE 10 (Other types not available yet)
- Included new PRG: "FOXYGETIMAGE.PRG". Include this file in your EXE project if you have images embedded in your EXE that you want to appear in your report. This may bring some security issues to your EXE, because this program will allow FoxyPreviewer to access the embedded images of your EXE.
- Fixed Issue "Emailto, subject and file name settings do not update" http://foxypreviewer.codeplex.com/workitem/9362
- Fix in ExcelListener: to allow flexible Currency and ()'s for Negative - by Rick Bean
- Fix in Report exporting, when some objects were not respecting the original report position (front and back) http://foxypreviewer.codeplex.com/workitem/9276
FoxyPreviewer Links
2012-03-16
Draw rotated strings with GdiPlusX
Gdi+ provides many ways to draw rotated objects, like images, strings and shapes. Basically, we need to do a translate transformation to the location of the text, then a rotate transform through the required angle, and then writing the text as normal using drawstring.
This is the definition found in MSDN: "The rotation operation consists of multiplying the transformation matrix by a matrix whose elements are derived from the angle parameter. This method applies the rotation by prepending it to the transformation matrix."
IMPORTANT:
All samples below use the new GDIPlus-X library, that is still in ALPHA version, but is already stable and reliable to do the majority of GDI+ tasks. Download the latest stable release from GitHub:
TIME TO PLAY
For the above sample I used a cool trick to rotate the text at the center of the image, and not at the left edge, that is the default behavior. Now it's up to you to make some tests. Try omitting the two lines that call the Graphics.TranslateTransform, change the angle, using negative and positive angles, etc...
In the "Samples" folder of the latest GDIPlus-X library version there's another cool sample, "Rotation.Scx", in which another translation technique was used. All the codes are in the "BeforeDraw" method of the ImageCanvas object.
DRAW ROTATED STRINGS IN YOUR REPORTS !

The real reason for this post was a request from my friend from Foxbrasil Emerson Santon Reed, when he asked about creating a watermark of a big text rotated in 45 degrees in a report.
So, my part was just to adapt the GDI+ code shown above to the ReportListener class and Report that he provided.
Run the code below to see a rotated text centered at 45 degrees in a report:
UPDATE 06-08-31
To make sure that the watermark will not be overwritten by opaque controls, the code below does the same thing shown above, but this time draws the watermark only after the footer band is totally rendered. The color used for this case was also changed from opaque to semitransparent, using Alpha of 128 ( 0 = totally transparent, 255 = opaque). So, here's the new ReportListener subclass to deal with this problem:
RELATED LINKS
Article from Bill Wagner http://www.ftponline.com/vsm/2002_10/online/csharp_bwagner_10_31_02/

IMPORTANT:
All samples below use the new GDIPlus-X library, that is still in ALPHA version, but is already stable and reliable to do the majority of GDI+ tasks. Download the latest stable release from GitHub:
* Initialize GdiPlus-X library DO LOCFILE("System.App") WITH _SCREEN.SYSTEM.Drawing * Create a new and empty Bitmap LOCAL loBmp as xfcBitmap loBmp = .Bitmap.New(180,180) * Create a Graphics object associated to the bitmap LOCAL loGfx as xfcGraphics loGfx = .Graphics.FromImage(loBmp) * Clear the BackGround of the image with light blue loGfx.Clear(.Color.FromRgb(230,230,255)) * Create a SolidBrush with Red Color LOCAL loBrush AS xfcBrush loBrush = .SolidBrush.New(.COLOR.FromRgb(255,0,0)) * The above statement could be also : * loBrush = .SolidBrush.New(.COLOR.FromARgb(255,255,0,0)) * loBrush = .SolidBrush.New(.COLOR.Red) * Create a Rectangle in which the rotated text will be drawn LOCAL loRect AS xfcRectangle loRect = .Rectangle.New(0, 0, loBmp.Width, loBmp.Height) * Get a basic string format object, then set properties LOCAL loStringFormat AS xfcStringFormat loStringFormat = .StringFormat.New() loStringFormat.ALIGNMENT = .StringAlignment.CENTER loStringFormat.LineAlignment = .StringAlignment.CENTER * Create a Font object LOCAL loFont AS xfcFont loFont = .FONT.New("Verdana",16, .FontStyle.Bold, .GraphicsUnit.POINT) * After creating all needed objects, we can apply the rotation * and draw the text * Translate and Rotate loGfx.TranslateTransform(loBmp.Width /2, loBmp.Height /2) loGfx.RotateTransform(-45) && angle of 45 degrees loGfx.TranslateTransform(-loBmp.Width /2, -loBmp.Height /2) * Finally, draw the string loGfx.DrawString("Rotated Text" + CHR(13) + CHR(10) + "GDIPlus-X is COOL !!!", ; loFont, loBrush, loRect, loStringFormat) * Reset Rotation loGfx.ResetTransform() * Save image to File loBmp.Save("c:\rotated.png", .Imaging.ImageFormat.Png) ENDWITH * Show created image RUN /N explorer.exe c:\rotated.png
TIME TO PLAY
For the above sample I used a cool trick to rotate the text at the center of the image, and not at the left edge, that is the default behavior. Now it's up to you to make some tests. Try omitting the two lines that call the Graphics.TranslateTransform, change the angle, using negative and positive angles, etc...
* Translate and Rotate loGfx.TranslateTransform(loBmp.Width /2, loBmp.Height /2) loGfx.RotateTransform(-45) && angle of 45 degrees loGfx.TranslateTransform(-loBmp.Width /2, -loBmp.Height /2)
In the "Samples" folder of the latest GDIPlus-X library version there's another cool sample, "Rotation.Scx", in which another translation technique was used. All the codes are in the "BeforeDraw" method of the ImageCanvas object.
DRAW ROTATED STRINGS IN YOUR REPORTS !
The real reason for this post was a request from my friend from Foxbrasil Emerson Santon Reed, when he asked about creating a watermark of a big text rotated in 45 degrees in a report.
So, my part was just to adapt the GDI+ code shown above to the ReportListener class and Report that he provided.
Run the code below to see a rotated text centered at 45 degrees in a report:
* Create a sample Report LOCAL i CREATE CURSOR dummy (fld1 c(20), fld2 c(15)) FOR i=1 TO 100 INSERT INTO dummy VALUES ("ReportListener with GdiPlus-X", "Visit CodePlex") ENDFOR SELECT dummy CREATE REPORT _testreport FROM dummy * Init GdiPlus-X DO LOCFILE("System.App") * Load Listener class LOCAL loreportlistener loreportlistener = CREATEOBJECT("MyReportListener") loreportlistener.LISTENERTYPE = 1 * Call the report using our listener REPORT FORM _testreport OBJECT loreportlistener USE IN dummy RETURN DEFINE CLASS myreportlistener AS _reportlistener OF ; ADDBS(HOME()) + "FFC\" + "_ReportListener.VCX" newPage = .T. oGdigraphics = NULL FUNCTION BEFOREREPORT DODEFAULT() This.ogdigraphics = _SCREEN.SYSTEM.drawing.graphics.new() ENDFUNC FUNCTION BEFOREBAND(nbandobjcode, nfrxrecno) #DEFINE FRX_OBJCOD_PAGEHEADER 1 IF nbandobjcode==frx_objcod_pageheader This.NewPage = .T. IF NOT This.issuccessor This.sharedgdiplusgraphics = This.GDIPLUSGRAPHICS ENDIF This.ogdigraphics.handle = This.sharedgdiplusgraphics ENDIF DODEFAULT(nBandObjCode, nFRXRecNo) ENDFUNC PROCEDURE RENDER(nfrxrecno,; nleft,ntop,nwidth,nheight,; nobjectcontinuationtype, ; ccontentstoberendered, gdiplusimage) WITH _SCREEN.SYSTEM.drawing IF This.NewPage * Create a SolidBrush with Red Color LOCAL lobrush AS xfcbrush lobrush = .solidbrush.new(.COLOR.fromRgb(255,64,64)) * Create a Rectangle in which the rotated text will be drawn LOCAL lorect AS xfcrectangle lorect = .rectangle.new(0, 0, This.sharedpagewidth,; This.sharedpageheight) * Get a basic string format object, then set properties LOCAL lostringformat AS xfcstringformat lostringformat = .stringformat.new() lostringformat.ALIGNMENT = .stringalignment.CENTER lostringformat.linealignment = .stringalignment.CENTER * Create a Font object LOCAL lofont AS xfcfont lofont = .FONT.new("Verdana",48, 0, .graphicsunit.POINT) * Translate and Rotate This.ogdigraphics.translatetransform(This.sharedpagewidth/2,; This.sharedpageheight/2) This.ogdigraphics.rotatetransform(-45) This.ogdigraphics.translatetransform(-This.sharedpagewidth/2,; -This.sharedpageheight/2) This.ogdigraphics.drawstring("Rotated Text" +CHR(13)+CHR(10)+; "GDIPlus-X is COOL !!!", ; lofont, lobrush, lorect, lostringformat) * Reset Rotation This.ogdigraphics.resettransform This.NewPage = .F. ENDIF ENDWITH DODEFAULT(nfrxrecno,; nleft,ntop,nwidth,nheight,; nobjectcontinuationtype, ; ccontentstoberendered, gdiplusimage) ENDPROC ENDDEFINE
UPDATE 06-08-31
To make sure that the watermark will not be overwritten by opaque controls, the code below does the same thing shown above, but this time draws the watermark only after the footer band is totally rendered. The color used for this case was also changed from opaque to semitransparent, using Alpha of 128 ( 0 = totally transparent, 255 = opaque). So, here's the new ReportListener subclass to deal with this problem:
DEFINE CLASS myreportlistener AS _reportlistener OF ; ADDBS(HOME()) + "FFC\" + "_ReportListener.VCX" ogdigraphics = NULL FUNCTION BEFOREREPORT DODEFAULT() This.ogdigraphics = _Screen.System.Drawing.Graphics.New() ENDFUNC FUNCTION AFTERBAND(nbandobjcode, nfrxrecno) *-- FRX OBJCODE column values #DEFINE FRX_OBJCOD_TITLE 0 #DEFINE FRX_OBJCOD_PAGEHEADER 1 #DEFINE FRX_OBJCOD_COLHEADER 2 #DEFINE FRX_OBJCOD_GROUPHEADER 3 #DEFINE FRX_OBJCOD_DETAIL 4 #DEFINE FRX_OBJCOD_GROUPFOOTER 5 #DEFINE FRX_OBJCOD_COLFOOTER 6 #DEFINE FRX_OBJCOD_PAGEFOOTER 7 #DEFINE FRX_OBJCOD_SUMMARY 8 #DEFINE FRX_OBJCOD_DETAILHEADER 9 #DEFINE FRX_OBJCOD_DETAILFOOTER 10 IF nbandobjcode==frx_objcod_pagefooter IF NOT This.issuccessor This.sharedgdiplusgraphics = This.GDIPLUSGRAPHICS ENDIF This.ogdigraphics.handle = This.sharedgdiplusgraphics WITH _SCREEN.SYSTEM.drawing * Create a SolidBrush with Red semi transparent color LOCAL lobrush AS xfcbrush lobrush = .solidbrush.new(.COLOR.fromArgb(128,255,128,128)) * Create a Rectangle in which the rotated text will be drawn LOCAL lorect AS xfcrectangle lorect = .rectangle.new(0, 0, This.sharedpagewidth,; This.sharedpageheight) * Get a basic string format object, then set properties LOCAL lostringformat AS xfcstringformat lostringformat = .stringformat.new() lostringformat.ALIGNMENT = .stringalignment.CENTER lostringformat.linealignment = .stringalignment.CENTER * Create a Font object LOCAL lofont AS xfcfont lofont = .FONT.new("Verdana",48, 0, .graphicsunit.POINT) * Translate and Rotate This.ogdigraphics.translatetransform(This.sharedpagewidth/2,; This.sharedpageheight/2) This.ogdigraphics.rotatetransform(-45) This.ogdigraphics.translatetransform(-This.sharedpagewidth/2,; -This.sharedpageheight/2) This.ogdigraphics.drawstring("Rotated Text" +CHR(13)+CHR(10)+; "GDIPlus-X is COOL !!!", ; lofont, lobrush, lorect, lostringformat) * Reset Rotation This.ogdigraphics.resettransform() ENDWITH ENDIF DODEFAULT(nBandObjCode, nFRXRecNo) ENDFUNC ENDDEFINE
RELATED LINKS
Article from Bill Wagner http://www.ftponline.com/vsm/2002_10/online/csharp_bwagner_10_31_02/
Captcha image generator with GdiPlusX
Recently I was navigating at Rick Strahl's Blog, when I found a very interesting post, "A Captcha Image generator for FoxPro", in which he shows how he created some captcha images. As Rick said in his post, "CAPTCHA basically displays a verification image next to a textbox that has to be filled out to validate the current request. It’s not a foolproof approach for validation and it has some issues with accessibility but it seems to be a common solution to this problem". The generated image contains a random text.
In that case, Rick chose to create a DLL in C++, and call it from VFP. Here, I'll show how to obtain the same results using the new GdiPlus-X library.
The codes below might be useful for other purposes too, because some interesting drawing techniques were used there.
To obtain the random string, I created a very simple function, that receives as parameter the quantity of characters that will be created:
TIME TO PLAY!
IMPORTANT:
All samples below use the new GDIPlus-X library. Download the latest stable release from Codeplex:
http://www.codeplex.com/Wiki/View.aspx?ProjectName=VFPX&title=GDIPlusX
SAMPLE 1 - CREATE A SIMPLE IMAGE AND DRAW SOME TEXT

The above sample creates the most simple image, containing a random string inside. To get the size needed to create the Bitmap, in a first moment I created a Graphics object from the _Screen. This object has only one task: to allow us to call the function MeasureString to obtain the size needed for our bitmap, as you can see below:
SAMPLE 2 - CREATE A SIMPLE IMAGE AND DRAW SOME TEXT ON A HATCH BACKGROUND

This is exactly the same sample we used before, but adding a hatch background. Gdi+ provides 52 different preset patterns for hatch brushes. In this sample, I chose to use a random one. Test it, and choose the one that you like most:
SAMPLE 3 - DRAW SOME TEXT ON A COLORED BACKGROUND USING RANDOM COLORS

This time I chose to create the background using a Solid Brush with a random backColor. For that I wanted to ensure to obtain a pastel color, so I used
INT(RAND() * 76) + 180 for each RGB color channel. This way, each channel will have at least the value 180, ensuring to obtain a bright color.
To draw each color in a different random color was easy too. Just looped through the string and drawn each character in its proportional position. This time, I wanted to have ANY random color, so that could be any value between 0 (RGB[0,0,0] - black) and 0xFFFFFF (RGB[255,255,255] - white).
SAMPLE 4 - DRAW SOME TEXT ON A COLORED HATCH BACKGROUND USING RANDOM COLORS WITH CHARACTER ROTATION

This time we did the same, but using a random Hatch Brush for the background, and aplying a slight rotation on each character. String rotation was already discussed in a previous post, anyway, here's a short explanation:
"System.Drawing" brings a method that permits to draw any object (shape, text, another image) using rotation, at any angle. For that we need to create a Matrix object, and use the "RotateAt" method, passing the center point of the object to be rotated. In this case, I needed to pass the center of the string.
WHAT MORE?

We can do some more things, like to draw some random lines on the image. One way could be just adding this simple code immediately before saving the image:
Although it is known that there are already some Captcha decoders, this might be useful for some situations. My goal in this post was just to show some techniques to create good captchas. There's a lot to be improved. You can use random and irregular fonts, other backgrounds, apply some transformations to characters, like scaling, shearing, blurring, halo effects. All this with GDI+ !
In the next release of the library, I hope to send some samples that create some interesting effects on texts, like the ones below, that might be useful for this too.

In that case, Rick chose to create a DLL in C++, and call it from VFP. Here, I'll show how to obtain the same results using the new GdiPlus-X library.
The codes below might be useful for other purposes too, because some interesting drawing techniques were used there.
To obtain the random string, I created a very simple function, that receives as parameter the quantity of characters that will be created:
PROCEDURE CreateString(tnLength) LOCAL lcText, lcChar, x, n lcText = "" FOR n = 1 TO tnLength x = INT(RAND() * 36) lcChar = IIF(x < 10, TRANSFORM(x), CHR(x + 55)) lcText = lcText + lcChar ENDFOR RETURN lcText ENDPROC
TIME TO PLAY!
IMPORTANT:
All samples below use the new GDIPlus-X library. Download the latest stable release from Codeplex:
http://www.codeplex.com/Wiki/View.aspx?ProjectName=VFPX&title=GDIPlusX


** Captcha Image Generator ** Creates a very simple image containing a string ** Based on Rick Strahl post on his weblog ** http://www.west-wind.com/wconnect/weblog/ShowEntry.blog?id=556 * The following sample demonstrates how to create a simple Captcha Generator LOCAL lcText AS Character lcText = CreateString(8) DO LOCFILE("System.App") LOCAL loBmp AS xfcBitmap LOCAL loFont AS xfcFont LOCAL loSizeF AS xfcSizeF LOCAL loScreenGfx AS xfcGraphics LOCAL loGfx AS xfcGraphics WITH _SCREEN.System.Drawing loScreenGfx = .Graphics.FromHwnd(_Screen.HWnd) loFont = .Font.New("Tahoma",20) && Font Name, Size * Obtain the width and height needed for the string loSizeF = loScreenGfx.MeasureString(lcText, loFont) * Create the new bitmap loBmp = .Bitmap.New(loSizeF.Width, loSizeF.Height) * Obtain a Graphics object to draw loGfx = .Graphics.FromImage(loBmp) loGfx.Clear(.Color.White) * Draw the whole String loGfx.DrawString(lcText, loFont, .Brushes.Black, 0, 0) * Save image to disk loBmp.Save("c:\captcha1.png", .Imaging.ImageFormat.Png) ENDWITH * Show image in Explorer RUN /n explorer.exe c:\captcha1.png RETURN PROCEDURE CreateString(tnLength) LOCAL lcText, lcChar, x, n lcText = "" FOR n = 1 TO tnLength x = INT(RAND() * 36) lcChar = IIF(x < 10, TRANSFORM(x), CHR(x + 55)) lcText = lcText + lcChar ENDFOR RETURN lcText ENDPROC
The above sample creates the most simple image, containing a random string inside. To get the size needed to create the Bitmap, in a first moment I created a Graphics object from the _Screen. This object has only one task: to allow us to call the function MeasureString to obtain the size needed for our bitmap, as you can see below:
loScreenGfx = .Graphics.FromHwnd(_Screen.HWnd) loFont = .Font.New("Tahoma",20) && FontName, FontSize * Obtain the width and height needed for the string loSizeF = loScreenGfx.MeasureString(lcText, loFont) * Create the new bitmap loBmp = .Bitmap.New(loSizeF.Width, loSizeF.Height)



** Captcha Image Generator ** Creates a very simple image containing a string on a hatch background ** Based on Rick Strahl post on his weblog ** http://www.west-wind.com/wconnect/weblog/ShowEntry.blog?id=556 * The following sample demonstrates how to create a simple Captcha Generator LOCAL lcText AS Character lcText = CreateString(8) DO LOCFILE("System.App") LOCAL loBmp AS xfcBitmap LOCAL loFont AS xfcFont LOCAL loSizeF AS xfcSizeF LOCAL loScreenGfx AS xfcGraphics LOCAL loGfx AS xfcGraphics WITH _SCREEN.System.Drawing loScreenGfx = .Graphics.FromHwnd(_Screen.HWnd) loFont = .Font.New("Tahoma",20) && Font Name, Size * Obtain the width and height needed for the string loSizeF = loScreenGfx.MeasureString(lcText, loFont) * Create the new bitmap loBmp = .Bitmap.New(loSizeF.Width, loSizeF.Height) * Obtain a Graphics object to draw loGfx = .Graphics.FromImage(loBmp) loGfx.Clear(.Color.White) liRand = INT(RAND()*52) + 1 * Fill the background of the rectangle with a random Hatch Brush loGfx.FillRectangle( ; .Drawing2D.HatchBrush.New(liRand, .Color.Black, .Color.White),; 0,0,loBmp.Width,loBmp.Height) * Draw the whole string loGfx.DrawString(lcText, loFont, .Brushes.Black, 0, 0) * Save image to disk loBmp.Save("c:\captcha2.png", .Imaging.ImageFormat.Png) ENDWITH * Show image in Explorer RUN /n explorer.exe c:\captcha2.png RETURN PROCEDURE CreateString(tnLength) LOCAL lcText, lcChar, x, n lcText = "" FOR n = 1 TO tnLength x = INT(RAND() * 36) lcChar = IIF(x < 10, TRANSFORM(x), CHR(x + 55)) lcText = lcText + lcChar ENDFOR RETURN lcText ENDPROC
This is exactly the same sample we used before, but adding a hatch background. Gdi+ provides 52 different preset patterns for hatch brushes. In this sample, I chose to use a random one. Test it, and choose the one that you like most:
* Fill the background of the rectangle with a random Hatch Brush loGfx.FillRectangle( ; .Drawing2D.HatchBrush.New(liRand, .Color.Black, .Color.White),; 0,0,loBmp.Width,loBmp.Height)



** Captcha Image Generator ** Creates a simple image containing a string ** Each character in a different random color ** in a random background color ** Based on Rick Strahl post on his weblog ** http://www.west-wind.com/wconnect/weblog/ShowEntry.blog?id=556 * The following sample demonstrates how to create a simple Captcha Generator LOCAL lcText AS Character lcText = CreateString(8) DO LOCFILE("System.App") LOCAL loBmp AS xfcBitmap LOCAL loFont AS xfcFont LOCAL loSizeF AS xfcSizeF LOCAL loScreenGfx AS xfcGraphics LOCAL loGfx AS xfcGraphics WITH _SCREEN.System.Drawing loScreenGfx = .Graphics.FromHwnd(_Screen.HWnd) loFont = .Font.New("Tahoma",20) && Font Name, Size * Obtain the width and height needed for the string loSizeF = loScreenGfx.MeasureString(lcText, loFont) * Obtain the width and height needed for the string loSizeF = loScreenGfx.MeasureString(lcText, loFont) LOCAL lnWidth, lnHeight, lnCharWidth lnWidth = loSizeF.Width * 1.5 && Stretch 50% to ensure rotated && characters will fit lnHeight = loSizeF.Height lnCharWidth = lnWidth / LEN(lcText) * Create the new bitmap loBmp = .Bitmap.New(lnWidth, lnHeight) * Obtain a Graphics object to draw loGfx = .Graphics.FromImage(loBmp) loGfx.Clear(.Color.White) * Fill the background of the rectangle with a random Solid Brush color * We need here a pastel color, so let's make each channel will be * at least 180 * Draw the background rectangle loGfx.FillRectangle( ; .SolidBrush.New(.Color.FromARGB(255, INT(RAND() * 76)+180, ; INT(RAND() * 76)+180, INT(RAND() * 76)+180)), ; 0,0,loBmp.Width,loBmp.Height) * Draw each character in a different random color LOCAL n, lcChar LOCAL loBrush AS xfcSolidBrush FOR n = 0 TO LEN(lcText) lcChar = SUBSTR(lcText, n, 1) * Create a brush with a random color loBrush = .SolidBrush.New(.Color.FromRGB(INT(RAND() * 0xFFFFFF))) * Draw the character loGfx.DrawString(lcChar, loFont, loBrush, (n-1) * lnCharWidth, 0) ENDFOR * Save image to disk loBmp.Save("c:\captcha3.png", .Imaging.ImageFormat.Png) ENDWITH * Show image in Explorer RUN /n explorer.exe c:\captcha3.png RETURN PROCEDURE CreateString(tnLength) LOCAL lcText, lcChar, x, n lcText = "" FOR n = 1 TO tnLength x = INT(RAND() * 36) lcChar = IIF(x < 10, TRANSFORM(x), CHR(x + 55)) lcText = lcText + lcChar ENDFOR RETURN lcText ENDPROC
This time I chose to create the background using a Solid Brush with a random backColor. For that I wanted to ensure to obtain a pastel color, so I used
INT(RAND() * 76) + 180 for each RGB color channel. This way, each channel will have at least the value 180, ensuring to obtain a bright color.
To draw each color in a different random color was easy too. Just looped through the string and drawn each character in its proportional position. This time, I wanted to have ANY random color, so that could be any value between 0 (RGB[0,0,0] - black) and 0xFFFFFF (RGB[255,255,255] - white).
* Create a brush with a random color loBrush = .SolidBrush.New(.Color.FromRGB(INT(RAND() * 0xFFFFFF)))
SAMPLE 4 - DRAW SOME TEXT ON A COLORED HATCH BACKGROUND USING RANDOM COLORS WITH CHARACTER ROTATION




** Captcha Image Generator ** Creates an image containing a string ** Each character in a different random color and a slight rotation ** Based on Rick Strahl post on his weblog ** http://www.west-wind.com/wconnect/weblog/ShowEntry.blog?id=556 * The following sample demonstrates how to create a simple Captcha Generator LOCAL lcText AS Character lcText = CreateString(8) DO LOCFILE("System.App") LOCAL loBmp AS xfcBitmap LOCAL loFont AS xfcFont LOCAL loSizeF AS xfcSizeF LOCAL loScreenGfx AS xfcGraphics LOCAL loGfx AS xfcGraphics WITH _SCREEN.System.Drawing loScreenGfx = .Graphics.FromHwnd(_Screen.HWnd) loFont = .Font.New("Tahoma",20) && Font Name, Size * Obtain the width and height needed for the string loSizeF = loScreenGfx.MeasureString(lcText, loFont) LOCAL lnWidth, lnHeight, lnCharWidth lnWidth = loSizeF.Width * 1.5 && Stretch 50% to ensure rotated && characters will fit lnHeight = loSizeF.Height lnCharWidth = lnWidth / LEN(lcText) * Create the new bitmap loBmp = .Bitmap.New(lnWidth, lnHeight) * Obtain a Graphics object to draw loGfx = .Graphics.FromImage(loBmp) loGfx.Clear(.Color.White) * Fill the background rectangle using a Hatch Brush with random pastel colors loGfx.FillRectangle( ; .Drawing2D.HatchBrush.New(INT(RAND()*52), ; .Color.FromARGB(255, INT(RAND() * 76)+180, INT(RAND() * 76)+180, INT(RAND() * 76)+180), ; .Color.FromARGB(255, INT(RAND() * 76)+180, INT(RAND() * 76)+180, INT(RAND() * 76)+180), ; 0,0,loBmp.Width,loBmp.Height) * Draw each character in a different random color * and a random rotation angle from -40 to +40 degrees LOCAL n, lcChar LOCAL loBrush AS xfcSolidBrush LOCAL loMatrix AS xfcMatrix FOR n = 0 TO LEN(lcText) lcChar = SUBSTR(lcText, n, 1) * Create a brush with a random color loBrush = .SolidBrush.New(.Color.FromRGB(INT(RAND() * 0xFFFFFF))) * Create a Matrix to apply rotation to the characters loMatrix = _Screen.System.Drawing.Drawing2D.Matrix.New() * Calculate random angle lnAngle = INT(RAND() * 80) - 40 * Rotate at the center of each character position loMatrix.RotateAt(lnAngle, ; .Point.New((n-1) * lnCharWidth + (lnCharWidth / 2), lnHeight / 2)) * Associate the Matrix to the Graphics object loGfx.Transform = loMatrix * Draw the character loGfx.DrawString(lcChar, loFont, loBrush, (n-1) * lnCharWidth, 0) ENDFOR * Save image to disk loBmp.Save("c:\captcha4.png", .Imaging.ImageFormat.Png) ENDWITH * Show image in Explorer RUN /n explorer.exe c:\captcha4.png RETURN PROCEDURE CreateString(tnLength) LOCAL lcText, lcChar, x, n lcText = "" FOR n = 1 TO tnLength x = INT(RAND() * 36) lcChar = IIF(x < 10, TRANSFORM(x), CHR(x + 55)) lcText = lcText + lcChar ENDFOR RETURN lcText ENDPROC
This time we did the same, but using a random Hatch Brush for the background, and aplying a slight rotation on each character. String rotation was already discussed in a previous post, anyway, here's a short explanation:
"System.Drawing" brings a method that permits to draw any object (shape, text, another image) using rotation, at any angle. For that we need to create a Matrix object, and use the "RotateAt" method, passing the center point of the object to be rotated. In this case, I needed to pass the center of the string.
* Create a Matrix to apply rotation to the characters loMatrix = .Drawing2D.Matrix.New() * Calculate random angle lnAngle = INT(RAND() * 80) - 40 * Rotate at the center of each character position loMatrix.RotateAt(lnAngle, ; .Point.New((n-1) * lnCharWidth + (lnCharWidth / 2), lnHeight / 2)) * Associate the Matrix to the Graphics object loGfx.Transform = loMatrix * Draw the character loGfx.DrawString(lcChar, loFont, loBrush, (n-1) * lnCharWidth, 0)
WHAT MORE?

LOCAL x1, y1, x2, y2, lnColor FOR n = 1 TO 10 x1 = RAND() * lnWidth x2 = RAND() * lnWidth y1 = RAND() * lnHeight y2 = RAND() * lnHeight lnColor = RAND() * 0xFFFFFF loGfx.DrawLine(.Pen.New(.Color.FromRGB(lnColor),1),x1, y1, x2, y2) ENDFOR
Although it is known that there are already some Captcha decoders, this might be useful for some situations. My goal in this post was just to show some techniques to create good captchas. There's a lot to be improved. You can use random and irregular fonts, other backgrounds, apply some transformations to characters, like scaling, shearing, blurring, halo effects. All this with GDI+ !
In the next release of the library, I hope to send some samples that create some interesting effects on texts, like the ones below, that might be useful for this too.

Be more productive with GdiPlusX
As I've read in one of Doug Hennig's great whitepapers, "IntelliSense, added in version 7, was likely the biggest productivity improvement ever added to VFP. It almost entirely eliminates the need to bring up the VFP help, even for little used commands and functions, because of how it provides tips on clauses and parameters in a context-sensitive manner".
When working with an extense library such as GdiPlus-X, Intellisense becomes really very important, because using it we can see easilly all the properties and methods for the current object. At this moment, there's no custom script for GdiPlus-X. However, we still can take huge benefits from that.
To see it working follow these steps:
1 - In the Command Window, or when initializing VFP, execute the code below and add it also to your source code:
DO LOCFILE("System.App")
If the GdiPlus-X library is not in your PATH, VFP will open a search window for you to point to the file "System.vcx"
2 - In the command window, type:
MODIFY COMMAND XYZ
3 - In the VFP Editor type the following:
LOCAL loBmp AS xfcBitmap
loBmp = _Screen.System.Drawing.Bitmap.FromFile(GETPICT())
loBmp.MakeT....
Very easy, isn't it ?
Basically, you have always to define your objects to have intellisense working for them, and to have the property / object "System" added to your VFP _Screen object.
You can still make things easier, creating your own Intellisense script, or just typing the following in the command window:
SET FUNCTION 5 TO "_Screen.System.Drawing"
and Voilá !
Every time that you type F5, automagically you'll be acessing all the "System.Drawing" namespace functions !
You can obtain more information about the library at this link: http://www.codeplex.com/VFPX/Wiki/View.aspx?title=GDIPlusX
If you want to participate in the project, coding, testing or reporting errors, feel free to use the Codeplex message boards and contact the GdiPlus-X team. Every help is very welcome.
Enjoy !
When working with an extense library such as GdiPlus-X, Intellisense becomes really very important, because using it we can see easilly all the properties and methods for the current object. At this moment, there's no custom script for GdiPlus-X. However, we still can take huge benefits from that.
To see it working follow these steps:
1 - In the Command Window, or when initializing VFP, execute the code below and add it also to your source code:
DO LOCFILE("System.App")
If the GdiPlus-X library is not in your PATH, VFP will open a search window for you to point to the file "System.vcx"
2 - In the command window, type:
MODIFY COMMAND XYZ
3 - In the VFP Editor type the following:
LOCAL loBmp AS xfcBitmap
loBmp = _Screen.System.Drawing.Bitmap.FromFile(GETPICT())
loBmp.MakeT....
Very easy, isn't it ?
Basically, you have always to define your objects to have intellisense working for them, and to have the property / object "System" added to your VFP _Screen object.
You can still make things easier, creating your own Intellisense script, or just typing the following in the command window:
SET FUNCTION 5 TO "_Screen.System.Drawing"
and Voilá !
Every time that you type F5, automagically you'll be acessing all the "System.Drawing" namespace functions !
You can obtain more information about the library at this link: http://www.codeplex.com/VFPX/Wiki/View.aspx?title=GDIPlusX
If you want to participate in the project, coding, testing or reporting errors, feel free to use the Codeplex message boards and contact the GdiPlus-X team. Every help is very welcome.
Enjoy !
Resizing images with GdiPlusX
GDI+ brings many possibilities for resizing images from Visual FoxPro9. In this short post, I'll show 3 techniques, that can be applied depending on your needs.
IMPORTANT:
All samples below use the GDIPlus-X library, a VFPX project on GitHub.

Here they are:
1 - THUMBNAIL
Using very few code we can resize any image:
** How To: RESIZE with Thumbnail Technique ** This code resizes an Image to size 60x60 ** Saves to a PNG file DO LOCFILE("System.App") && GdiPlusX project on Github WITH _SCREEN.System.Drawing * Variables to store the new Image size LOCAL lnWidth, lnHeight STORE 60 TO lnWidth, lnHeight * Load the original Image LOCAL loSrcImage as xfcBitmap loSrcImage = .Bitmap.New(GETPICT()) * Get the thumbnail with the desired size LOCAL loThumbnail as xfcImage loThumbnail = loSrcImage.GetThumbnailImage(lnWidth, lnHeight) * Save the resized image as Png loResized.Save("c:\Resized1.png", .Imaging.ImageFormat.Png) ENDWITH RETURN
But there is a problem, that some kinds of Images like JPEGS may store embedded thumbnails. The function "GetThumbnailImage" in a first moment searches at the image file if there already exists a thumbnail stored. If yes, GDI+ will scale this image to the desired dimensions, causing some huge distortions.
In my opinion, this technique is recommended to be used to resize to small images, not bigger than 72x72.
2 - BITMAP LOADING
This code is also really short.
** How To: RESIZE with Bitmap loading Technique ** The code resizes an Image to size 60x60 ** Saves to a PNG file DO LOCFILE("System.App") WITH _SCREEN.System.Drawing * Variables to store the new Image size LOCAL lnWidth, lnHeight STORE 60 TO lnWidth, lnHeight * Load the original Image LOCAL loSrcImage as xfcBitmap loSrcImage = .Bitmap.New(GETPICT()) * Get the resized version of the image LOCAL loResized as xfcBitmap loResized = .Bitmap.New(loSrcImage, lnWidth, lnHeight) * Save the resized image as Png loResized.Save("c:\Resized2.png", .Imaging.ImageFormat.Png) ENDWITH RETURN
The Bitmap class provides many overloads, that you can see at
http://msdn2.microsoft.com/en-us/library/System.Drawing.Bitmap.Bitmap%28vs.80%29.aspx
. One of them permits to load from any GDI+ image object, and automagically resizes it to the desired dimensions. Very easy, but it uses some default GDI+ properties, and does NOT ensure to generate the best possible quality.
This option is recommended for almost all cases when you need to rescale an image to a smaller size than the original.
3 - CUSTOMIZED RESIZING
The code below is longer, but permits to have a considerable control over the way our image is resized.
** How To: RESIZE with Image Drawing Technique ** The code resizes an Image to size 60x60 ** Saves to a PNG file ** GDI+ gives you considerable control over the way your image is resampled, ** so it makes sense to take advantage of this flexibility. ** This way, we can obtain the most satisfactory image quality DO LOCFILE("System.App") WITH _SCREEN.System.Drawing * Variables to store the new Image size LOCAL lnWidth, lnHeight STORE 60 TO lnWidth, lnHeight * Load the original Image LOCAL loSrcImage as xfcBitmap loSrcImage = .Bitmap.New(GETPICT()) * Create a New Image with the desired size LOCAL loResized as xfcBitmap loResized = .Bitmap.New(lnWidth, lnHeight, ; .Imaging.PixelFormat.Format32bppARGB) * Set the image resolution to be the same as the original loResized.SetResolution(loSrcImage.HorizontalResolution, ; loSrcImage.VerticalResolution) * Obtain a Graphics object to get the rights to draw on it LOCAL loGfx as xfcGraphics loGfx = .Graphics.FromImage(loResized) * Set some properties, to ensure to have a better quality of image loGfx.SmoothingMode = .Drawing2D.SmoothingMode.HighQuality loGfx.InterpolationMode = .Drawing2D.InterpolationMode.HighQualityBicubic * Draw the source image on the new image at the desired dimensions loGfx.DrawImage(loSrcImage, 0, 0, lnWIdth, lnHeight) * Save the resized image as Png loResized.Save("c:\Resized3.png", .Imaging.ImageFormat.Png) ENDWITH RETURN
In this code we are controlling the Image Resolution, the Interpolation Mode, and the Smoothing Mode of the scaled image.
According to Libor Tinka, "Interpolation refers to how data is interpolated between endpoints. In its simplest form, to interpolate is to estimate a missing value by taking an average of known values at neighboring points."
In general the bicubic interpolation is used when more accuracy that's why I chose to use this mode for this sample.
Because of all these settings, this is the better option for resizing images to a bigger size than the original.
Libor Tinka wrote a very interesting article on this subjects, and provides many code samples and images showing the results that can be obtained using different Interpolation modes. Strongly recommended !
http://www.codeproject.com/csharp/imgresizoutperfg...
Subscribe to:
Posts (Atom)