smithvoice.com
 Y'herd thisun? 

“Scientists at the Space Applications Centre (SAC), using data gathered from Chandrayaan-I's Terrain Mapping Camera and Hyper Spectral Imager (HySI) payloads, found a 1.2 km long buried, uncollapsed and near horizontal lava tube.

The scientists said the lava tubes offer a dust-free environment and adapting them for human use requires minimal construction.

The structure[s] also shields its occupants as after 6 meters depth, no effects of radiation due to or induced by galactic cosmic rays were observed in simulation, they said. "After less than one meter, no effects of radiation due to or induced by solar particle events are observable. Natural or induced radioactivity does not play a significant role in the lava tube exposures," the scientists said.”


from ISRO finds cave on moon; can be used as human outpost, Feb 24 2011 by Indian Defense News

Your own image control and App part 11

TaggedCoding, VB, Imaging

11) Handling the unhandleable exception

Even though the message calls this an "unhandled exception", the truth is that it's unhandle-able. If you step through the code you'll easily track down where it happens (in UpdateZoomDisplay right after you change the picturebox size) but you'll also see that the current catch-all exception handler is getting the message so adding a catch for System.ComponentModel.Win32Exception also won't work. Plus, you have a catch-all down the stack in the testHarness's button click event and it's not recognizing the exception either.

To quickly see the stack dump, play end-user on a debug build. Rebuild the solution and run the exe outside of Visual Studio then zoom till lightning strikes:

Looks like it's pretty far down the food chain in the low windows messages, pretty far out of the control of a .Net language. I've asked a lot of people about this over the years and while there are rule-of-thumb API explanations no one from Redmond has publicly given an official statement. When that happens you may as well call it "A Bug".

I wish I could tell you that there's a way to watch the settings and prevent it from happening just-in-time on all machines with a simple algorithm with pure managed code but I can't. I have come up with a list of magic numbers that can be applied against WMI Video display information but while such a tactic can work WMI is only available on certain operating systems and checking all the possible settings isn't fast. There simply isn't a happy solution to this one, so we'll have to punt.

The maximum width/height has nothing to do with your images, and isn't even an issue specific to a picturebox. All painted controls including buttons, listboxes trees and on and on all will crash your app at the same large sizes and the old "bug that is by design" of GUI control size properties is not protection against fatal exceptions.

Have you ever noticed that GUI controls (buttons, panels, pictureboxes, etc.) have a Size property of Width and Height Shorts (Int16)? The documentation calls them Integers (Int32) so by definition they should allow you to set the values up to 2147483647, but when you do the runtime immediately sets them to 32767 again (Short.MaxValue). It doesn't raise an exception because according to the documentation they're 32bit integers, but under the hood they're being mapped to 16bit and the runtime will automatically perform a fixed-value narrowing (actually a truncation) on any larger settings. You can test this quickly without code by playing with a control's size properties right in the IDE property sheet.

Some people consider this silent alteration to be an obnoxious way to run a development platform. I'd be happy with it if it kept things from crashing, but it doesn't; If you try to set a control to 32767x32767 you're still going to crash.

As I see it from testing, the actual maximum width/height values depend on the current color depth (8bit 256 color, 16bit "high color" or 32bit "true color" as specified in your display settings and also the video controller's native resolution (standard 4:3 or the new widescreen native modes on laptops). Note that the native resolution of the controller is what is important because even if you set a widescreen display to a standard screen resolution such as 1024x768 you're only interpolating the native pixels for the display, not altering the controller's native resolution.

Making thing even more difficult, the limit of one dimension (such as the width) changes as you increase or decrease the other dimension and the combined value is not a constant. For example, on a 4:3 native controller set to 32bit color depth, you can set a button or picturebox to be a square at 16095x16095 and all is well but using 16096x16096 you'll crash. That implies a limit is 16095+16095=32190. Thing is you can set the control to the equivalent of 16096+16096=32192 by using 16228x15964 without the fatal exception ... and to add to the weirdness if you try to set the size to 16227x15965 you'll crash. The values move in a less-than-logical way, and are completely different at different color depths and different controller native resolutions.

In case you're wondering, the betas of .Net2 add nothing to address this OS issue.

Sticking to RAD options we have two primary contenders: (1) Use DrawImage's ability to blit sections of a source image to a new bitmap with scaling; (2) Go for a common denominator size limit that seems to work on all testable displays even if that limit is far less than what any particular user's controller settings can handle.

Using DrawImage is one to try if lowballing doesn't make users happy. It makes programming more complex in that you lose the helpful AutoScroll scrollbars and will have to duplicate their abilities. This duplication will mean on the fly creation of bitmaps beyond the viewable area and even with doublebuffering you'll probably not ever reach the smoothness of AutoScroll. We've all used systems that do this kind of trickery, such as web-based map sites that make you click big side arrows and wait for the redraws rather than providing a nice smooth scrolling. (You can make things faster by dropping the un-accelerated GDI+ for GDI but then you're back to the pre-.Net TIF problems.)

Looks like a common denominator will be the fastest way to market, all we have to do is come up with the values. For that you'll have to do testing and because of the fatal exception the tests will have to be done manually (even having a controller exe shell a controlled exe with commandlines and a flag file won't work as an automated test since the exception will bail the controlled exe before it can unset the flag). I'll save you the time and give you values that I've come up with: For 16bit 18000 and for 32bit 13000, the safe limits being slightly higher for 4:3 native controllers but these values accommodate widescreen controllers that are the latest fad in notebooks.

Ints or Shorts? Does it matter?

With all the details we now know about image sizes, the question of value types is worth pondering. We know that our source images can't exceed 32767 (Short.MaxValue) in either dimension, and that means that the ZoomPercent can't exceed 32767 either because if you have a source image at 32767x5 the width already limits any greater zooming. So should we change out ZoomPercent to a 16bit integer (Int16/Short)? If we don't we're making things just as confusing as Microsoft does with Visual Studio's control size properties, and if we do are we asking for a performance hit when .Net internally converts the Shorts to Ints to best work with a 32bit operating system?

This is an interesting and often debated question. If your experience goes back to Win3x and/or DOS then you remember worrying over every bit of memory and taking great pains to use the smallest safe types possible, but then with Win32 and cheaper RAM you probably did so many API calls that you just got in the habit of using Int32s (called "Longs" in VB3-6) all the time. Because of the theoretical portability of .Net, some folks say that the clue to best practice is found in the max value of the "Integer" alias, with the thinking that for best performance "Integer" would convert to Int32 on 32bit Windows and Int64 on 64bit OSes, but now that 64bit Windows is here and 64bit .Net2 is in beta, that old logic isn't holding up. As I read it in the MSIL discussions, "Integer" is still an Int32 even when compiling with the 64bit switches (at least with the current .Net2 beta as of March 2005). Further reading says that technically there are no Int32s, Int16s or Int64s in the IL and instead all there are Pointers to memory addresses and there have been deductions on more than one occasion to the effect of there being no tangible performance benefit to defaulting numeric variables to Int32s just because we're using a 32bit OS; These experts are of the impression that all Ints perform essentially at the same level (in fact I remember one post where the dev stated he was using all Doubles internally and only converting them as needed for method/API calls).

With the geeks not able to see a clear benefit to defaulting to Int32s on a 32bit OS, there seems to be merit in exposing the ZoomPercent and holding its related variables as Shorts. But before you start changing code ...

All of a sudden we see why Microsoft just made everything Integers whether it technically made sense or not for a specific situation. If we start being more logical with Shorts for zooming, shouldn't we also change the variables and properties related to PageNumbers? In the real world I doubt if anyone will ever be processing a file containing over 32767 pages ... or even 256 for that matter, which means a Byte type would probably suffice. In this case I'd say stick with the Int32 because under the hood we'd just end up converting every time we ping the array ... and now from a big picture view our special zoom Short is sticking out like a sore thumb. Right or wrong defaulting to Int32s is the standard that dev-users expect most everything to be and worrying over memory isn't a big deal in a RAD tool (the Runtime takes care of memory, that's part of the contract and the definition of a RAD tool, after all).

Decision made, decision justified: "Integers" will be our default numeric types BUT unlike Visual Studio we'll protect our code from the fatal exception.

Add a new sub-region called "Constants" to the Declarations region of the UC and enter those limits plus specify a value for the minimum zoom issue we mentioned earlier. In the Private Variables sub-region, add an Int to hold the current color depth:

#Region "Declarations"
 
#Region "Constants"
Private Const MAX_CONTROL_DIMENSION_16BIT As Integer = 18000
Private Const MAX_CONTROL_DIMENSION_32BIT As Integer = 13000
Private Const MIN_ZOOM_PIXELS As Integer = 2
#End Region
 
#Region "Private Variables"
Private m_ImagePages() As svImagePage
Private m_CurrentPageNumber As Integer = 0
 
'default to the safest limit
Private m_MaxControlDimension As Integer = MAX_CONTROL_DIMENSION_32BIT
 
#End Region
#Region "Events"
...
 

We don't have to deal with both color depths, just limiting to the 32bit max would take care of everything lower. I chose to go the extra mile because in my experience corporate users run less powerful machines than developers and usually are stuck with 16bit, and in the case that's an advantage in that they can zoom in farther. Since corporate end users will likely be the people using this control most let's give them the added boost if we can.

The most logical time to check the color depth is when the control is loaded. This is the first time we've done any initialization code in the UC but it won't be the last so we'll plan for the future by creating a new sub in the "Private Methods" region named InitColorDepth and calling it from the UC's Load event (get the Load stub using the IDE combos):

#Region "Private Methods"
Private Sub svImageEditor_Load(ByVal sender As Object, _
ByVal e As System.EventArgs) Handles MyBase.Load
InitColorDepth()
End Sub
 
Private Sub InitColorDepth()
Try
Select Case ColorDepth()
Case 32
m_MaxControlDimension = MAX_CONTROL_DIMENSION_32BIT
Case Else
'takes care of 16 and lower,
'less than 16 shouldn't come up often enough
'to need special code for it
m_MaxControlDimension = MAX_CONTROL_DIMENSION_16BIT
End Select
Catch ex As Exception
Throw ex
End Try
End Sub
 
Private Function SourceFilePageCount(ByVal sourceImage As Image) As Integer
...

Unfortunately .Net doesn't give us the color depth information so we will have to use P/Invoke to get it. Add a new module to the UC project, name it "Win32.vb" and paste in the following API code:

Module Win32
#Region "Declarations"
Private Const PLANES As Integer = 14
Private Const BITSPIXEL As Integer = 12
Private Declare Function GetDeviceCaps Lib "gdi32" (ByVal hdc As Integer, _
ByVal nIndex As Integer) As Integer
Private Declare Function GetDC Lib "user32" (ByVal hWnd As Integer) As Integer
Private Declare Function ReleaseDC Lib "user32" (ByVal hWnd As Integer, _
ByVal hdc As Integer) As Integer
#End Region
#Region "Methods"
Public Function ColorDepth() As Integer
'code by "Richard from the UK"
'originally posted at: http://tinyurl.com/69gz2
Dim nPlanes As Integer
Dim BitsPerPixel As Integer
Dim dc As Integer
Try
 dc = GetDC(0)
 nPlanes = GetDeviceCaps(dc, PLANES)
 BitsPerPixel = GetDeviceCaps(dc, BITSPIXEL)
 ReleaseDC(0, dc)
 Return nPlanes * BitsPerPixel
Catch ex As Exception
 'Richard chose to set to a "safe" result
 'nPlanes = 1
 'BitsPerPixel = 8
 'Return nPlanes * BitsPerPixel
 'after testing, decide if that makes sense
  
 'in our case where we're limiting based on the result, 
 'we'd best err on the most restrictive
 Return 32
End Try
End Function
#End Region
End Module

We'll need to test our zoomed dimension values before applying them to the picturebox and if the largest source dimension exceeds the magic number then we'll force the zoom to a limited percentage. We'll take care of hitting the minimum zoom value in the same function. Click into the UC's "Private Methods" region and paste in that routine:

 

Private Sub SetZoomValues(ByVal value As Integer)
'test each dimension to see if the zoom
'exceeds the Max limit for the color depth and/or
'the minimum display size from MIN_ZOOM_PIXELS
Dim finalZoom As Integer
Dim hitMin, hitMax As Boolean
'pull out the current imagepage, rather than pinging the array every time
Dim tmpImagePage As svImagePage = m_ImagePages(m_CurrentPageNumber)
'as long as we're fussing over dots, hold a ref to the image
Dim tmpWorkingImage As Image
tmpWorkingImage = tmpImagePage.WorkingImage
Dim tmpZoom As Integer = tmpImagePage.CurrentZoom + value
'the larger dimension sets the pace
If tmpWorkingImage.Width > tmpWorkingImage.Height Then
If (tmpWorkingImage.Width * tmpZoom \ 100) > m_MaxControlDimension Then
'the picturebox width will be set to the limit
'calculate what the percentage would be
finalZoom = CInt((m_MaxControlDimension / tmpWorkingImage.Width) * 100)
hitMax = True
Else
If (tmpWorkingImage.Width * tmpZoom \ 100) < MIN_ZOOM_PIXELS Then
'can't be smaller than MIN_ZOOM_PIXELS
'calculate the percent zoom of MIN_ZOOM_PIXELS
finalZoom = CInt((MIN_ZOOM_PIXELS / tmpWorkingImage.Width) * 1000)
hitMin = True
Else
finalZoom = tmpImagePage.CurrentZoom + value
End If
End If
Else
'this is duplication of the above code but against the Height
'be careful to keep it in synch and to get all of the
'"Width"s changed to "Height"s
If (tmpWorkingImage.Height * tmpZoom \ 100) > m_MaxControlDimension Then
'the picturebox height will be set to the limit
'calculate what the percentage would be
finalZoom = CInt((m_MaxControlDimension / tmpWorkingImage.Height) * 100)
hitMax = True
Else
If (tmpWorkingImage.Height * tmpZoom \ 100) < MIN_ZOOM_PIXELS Then
'can't be smaller than MIN_ZOOM_PIXELS
'calculate the percent zoom of MIN_ZOOM_PIXELS
finalZoom = CInt((MIN_ZOOM_PIXELS / tmpWorkingImage.Height) * 1000)
hitMin = True
Else
finalZoom = tmpImagePage.CurrentZoom + value
End If
End If
End If
With tmpImagePage
.CurrentZoom = finalZoom
.MinDimensionReached = hitMin
.MaxDimensionReached = hitMax
End With
'shoot the temp imagepage into the slot
m_ImagePages(m_CurrentPageNumber) = tmpImagePage
End Sub

 

SetZoomValues is only half as complex as it looks. The code is doubled because the finalZoom value is based on the larger of the two source dimensions. Both halves just drill down to check first if a new zoom value will exceed the Max limit and if so a limiting Zoom percentage is calculated and a boolean flag "hitMax" is set. If the Max isn't hit then the next check is to see if the Min limit is going to be hit and if so then that zoom percentage is calculated and a bool "hitMin" is set. If the zoom is safely between the limits then it is simply applied to the current zoom value. (As the comments state, be careful if you start refactoring to make sure that the Width and Height parts stay in synch.)

At the end of the sub we're popping the temporary svImagePage back into the array and notice that we've got a couple new members to the svImage structure: MinDimensionReached and MaxDimensionReached being set to the hitMin and hitMax bools. These new members, when attached to the svImageZoomChangeEventArgs object, will make GUI development easy for our dev-user; When the ZoomChanged event is raised they can set the .Visible properties of zoom buttons directly to these values and instantly make their forms more user-friendly.

In svTypes.vb, add the new members to the svImagePage structure:

Friend Structure svImagePage
Dim OriginalImage As Image
Dim WorkingImage As Image
Dim CurrentZoom As Integer
Dim MinDimensionReached As Boolean
Dim MaxDimensionReached As Boolean
End Structure

And update the svImageZoomChangeEventArgs class in svEventArguments.vb:

 

Public Class svImageZoomChangeEventArgs
Inherits EventArgs
Private m_CurrentZoom As Integer = 100
Private m_MinLimitReached As Boolean = False
Private m_MaxLimitReached As Boolean = False
Public Sub New(ByVal currentZoom As Integer, _
ByVal reachedMinLimit As Boolean, ByVal reachedMaxLimit As Boolean)
MyBase.New()
m_CurrentZoom = currentZoom
m_MinLimitReached = reachedMinLimit
m_MaxLimitReached  = reachedMaxLimit
End Sub
 
Public ReadOnly Property CurrentZoom() As Integer
Get
Return m_CurrentZoom
End Get
End Property
  
Public ReadOnly Property MinLimitReached() As Boolean
Get
Return m_MinLimitReached
End Get
End Property
  
Public ReadOnly Property MaxLimitReached() As Boolean
Get
Return m_MaxLimitReached
End Get
End Property
End Class

 

Y'know, an alternative route could be to calculate the maximum zoom percentage during the ImageLoad and hold that percentage in a member of the svImagePage. If we did that then SetZoomValues would only need to check if a new zoom percentage exceeds the precalculated limit. While there wouldn't be a noticeable performance difference even on a relatively slow machine, comparing two values is more direct than doing division AND comparing every time the Zoom is changed, and cutting complexity is always a good thing. What's more we wouldn't need the MinDimensionReached and MaxDimensionReached structure variables anymore... instead we could just raise a fast comparison of CurrentZoom to the Max and Min percentages, that means even less complexity and less chances for bugs.

Replace the MinDimensionReached and MaxDimensionReached bools with integer members named "MinZoomPercent" and "MaxZoomPercent" to the svImagePage type:

 

Friend Structure svImagePage
Dim OriginalImage As Image
Dim WorkingImage As Image
Dim CurrentZoom As Integer
Dim MinZoomPercent As Integer
Dim MaxZoomPercent As Integer
End Structure

 

Now create a new private function for the up-front calculations.

If you do some quick math you'll see that very large images can have minimum zooms of less than one percent (and obviously greater than zero), and when those results are moved to the Integer structure member they'll be narrowed to zero. That will mean that the images will be too small to see in the picturebox which was the reason for the MIN_ZOOM_PIXELS constant to begin with. We'll do a safety net for this unlikely situation and force a zero result to one, this will make the picturebox display relatively large depending on the exact size of the massive source image but it's better than making the structure members floats/singles just to account for exceptional images.

 

Private Sub CalcZoomPercentLimits(ByRef imagePage As svImagePage)
'unrealistically large images will need fractional minimums
'we will round up fractionals to a 1 for our integer MinZoomPercent
Dim tmpMin As Integer = 0
'the larger dimension sets the pace
If imagePage.WorkingImage.Width > imagePage.WorkingImage.Height Then
tmpMin = _
CInt((MIN_ZOOM_PIXELS / imagePage.WorkingImage.Width) * 1000)
If tmpMin = 0 Then
imagePage.MinZoomPercent = 1
Else
imagePage.MinZoomPercent = tmpMin
End If
imagePage.MaxZoomPercent = _
CInt((m_MaxControlDimension / imagePage.WorkingImage.Width) * 100)
Else
tmpMin = _
CInt((MIN_ZOOM_PIXELS / imagePage.WorkingImage.Height) * 1000)
If tmpMin = 0 Then
imagePage.MinZoomPercent = 1
Else
imagePage.MinZoomPercent = tmpMin
End If
imagePage.MaxZoomPercent = _
CInt((m_MaxControlDimension / imagePage.WorkingImage.Height) * 100)
End If
End Sub

Next, clean up the LoadImage routine with that up front call:

 

...
If pageCount > 1 Then
For i As Integer = 0 To UBound(m_ImagePages)
tmpFileImage.SelectActiveFrame(FrameDimension.Page, i)
tmpFrameImage = DirectCast(tmpFileImage.Clone, Image)
tmpFrameImage = CopyImage(tmpFrameImage)
m_ImagePages(i).OriginalImage = tmpFrameImage
m_ImagePages(i).WorkingImage = DirectCast(tmpFrameImage.Clone, Image)
m_ImagePages(i).CurrentZoom = 100
CalcZoomPercentLimits(m_ImagePages(i))
Next
Else
m_ImagePages(0).OriginalImage = tmpFileImage
m_ImagePages(0).WorkingImage = DirectCast(tmpFileImage.Clone, Image)
m_ImagePages(0).CurrentZoom = 100
CalcZoomPercentLimits(m_ImagePages(0))
End If
...

And lastly trim down the complexity of SetZoomValues:

 

Private Sub SetZoomValues(ByVal value As Integer)
'test each dimension to see if the zoom
'exceeds the Max limit for the color depth and/or
'the minimum display size from MIN_ZOOM_PIXELS
Dim finalZoom As Integer
'pull out the current imagepage, rather than pinging the array every time
Dim tmpImagePage As svImagePage = m_ImagePages(m_CurrentPageNumber)
Dim tmpZoom As Integer = tmpImagePage.CurrentZoom + value
If tmpZoom >= tmpImagePage.MaxZoomPercent Then
finalZoom = tmpImagePage.MaxZoomPercent
ElseIf tmpZoom <= tmpImagePage.MinZoomPercent Then
finalZoom = tmpImagePage.MinZoomPercent
Else
finalZoom = tmpZoom
End If
tmpImagePage.CurrentZoom = CInt(finalZoom)
'shoot the temp imagepage into the slot
m_ImagePages(m_CurrentPageNumber) = tmpImagePage
End Sub

Ready for some good news? Right now we have the ZoomChanged event being raised in three places: the ZoomPercent and CurrentPageNumber property sets and ResetImage. That's a maintenance problem we can avoid by ripping it out of all three places and having it come only from UpdateZoomDisplay. Here's that new code:

 

Private Sub UpdateZoomDisplay()
Dim tmpWorkingImage As Image
Try
tmpWorkingImage = m_ImagePages(m_CurrentPageNumber).WorkingImage
pbDisplay.Width = tmpWorkingImage.Width * m_ImagePages(m_CurrentPageNumber).CurrentZoom \ 100
pbDisplay.Height = tmpWorkingImage.Height * m_ImagePages(m_CurrentPageNumber).CurrentZoom \ 100
pbDisplay.Image = tmpWorkingImage
RaiseEvent ZoomChanged(Me, New svImageZoomChangeEventArgs( _
m_ImagePages(m_CurrentPageNumber).CurrentZoom, _
m_ImagePages(m_CurrentPageNumber).CurrentZoom <= _
m_ImagePages(m_CurrentPageNumber).MinZoomPercent, _
m_ImagePages(m_CurrentPageNumber).CurrentZoom >= _
m_ImagePages(m_CurrentPageNumber).MaxZoomPercent) _
)
Catch ex As Exception
'Todo: remove after testing
Throw ex
Finally
If Not tmpWorkingImage Is Nothing Then
tmpWorkingImage = Nothing
End If
End Try
 End Sub

And here are the "final" versions of the related property sets, with the event removed and a more intuitive call to UpdateZoomDisplay inserted:

 

Public Property CurrentPageNumber() As Integer
'current page is held internally as zero-based
'we expose as one-baed
Get
If Not m_ImagePages Is Nothing AndAlso m_ImagePages.Length > 0 Then
Return m_CurrentPageNumber + 1
Else
Return 0
End If
End Get
Set(ByVal Value As Integer)
If Not m_ImagePages Is Nothing AndAlso m_ImagePages.Length > 0 Then
If Value <= TotalPageCount And Value > 0 Then
Dim initialPage As Integer = m_CurrentPageNumber + 1
m_CurrentPageNumber = Value - 1
UpdateZoomDisplay()
'tell the host
RaiseEvent PageChanged(Me, _
New svImagePageChangeEventArgs(TotalPageCount, initialPage, m_CurrentPageNumber + 1))
Else
Throw New ArgumentOutOfRangeException("Page " & Value.ToString & _
" does not exist (file contains " & TotalPageCount.ToString & " pages)")
End If
End If
End Set
End Property
Public Property ZoomPercent() As Integer
Get
If Not m_ImagePages Is Nothing AndAlso m_ImagePages.Length > 0 Then
Return m_ImagePages(m_CurrentPageNumber).CurrentZoom
Else
'default for empty control
Return 100
End If
End Get
Set(ByVal Value As Integer)
If Not m_ImagePages Is Nothing AndAlso m_ImagePages.Length > 0 Then
SetZoomValues(Value)
UpdateZoomDisplay()
End If
End Set
End Property

ResetImage is going to have to have an overhaul. Currently it only deals with Rotations by replacing the svImagepage.WorkingImage with a fresh clone of the OriginalImage, but now it makes sense to let it force zooms back to 100%. We could overload it but there'd be no signature difference between a Rotation-only and a Zoom-only version so going with optionals works better:

Public Sub ResetImage(Optional ByVal resetRotation As Boolean = True, _
Optional ByVal resetZoom As Boolean = True, _
Optional ByVal resetAll As Boolean = False)
If Not m_ImagePages Is Nothing AndAlso m_ImagePages.Length > 0 Then
If resetAll Then
For i As Integer = 0 To UBound(m_ImagePages)
If resetRotation Then
 m_ImagePages(i).WorkingImage = _
 DirectCast(m_ImagePages(i).OriginalImage.Clone, Image)
End If
If resetZoom Then
 m_ImagePages(i).CurrentZoom = 100
End If
Next
Else
If resetRotation Then
m_ImagePages(m_CurrentPageNumber).WorkingImage = _
DirectCast(m_ImagePages(m_CurrentPageNumber).OriginalImage.Clone, Image)
End If
If resetZoom Then
m_ImagePages(m_CurrentPageNumber).CurrentZoom = 100
End If
End If
UpdateZoomDisplay()
End If
End Sub

 

Next: Fixing the squish

Robert Smith
Kirkland, WA

added to smithvoice march 2005


jump to:

  • 1) The spec
  • 2) Setting up the workspace
  • 3) Feature 1: Loading an image
  • 4) Custom Exceptions
  • 5) "Fax images" and Multipage TIFFs
  • 6) Custom events
  • 7) Selecting specific fax pages
  • 8) Feature 2: Rotating image displays
  • 9) The most useful tool in GDI+: DrawImage
  • 10) Feature 3: Zooming
  • 11) Handling the unhandleable exception
  • 12) Fixing the squish
  • 13) Zooming to fit the control
  • 14) You're already beating the Pros
  • 15) Feature 4: Cropping
  • 16) Bonus Feature: StickyMouse
  • 17a) Final Cleanup
  • 17b) Passing the current display image
  • 18) Making the application
  • 19) Source and result viewports
  • 20) A better toolbar
  • 21) Hooking the toolbar to the project
  • 22) Adding ImageEditors
  • 23) The toolbar ZoomCombo
  • 24) The final solution
  • 25) Saving to image files
  • 26) An integer-only textbox
  • 27) Passing save options between forms
  • 28) Dealing with that last exception
  • 29) Offer more options with menus
  • 30 The downloads and ebook


  • home     who is smith    contact smith     rss feed π
    Since 1997 a place for my stuff, and it if helps you too then all the better smithvoice.com