Random file access and LoadPicture

Horst

Member
Licensed User
Longtime User
Hello,

there seems to be a problem at using a random file access and LoadPicture on the same file:

fn(0) = "C:\Tmp\Holz.jpg"
fn(1) = "C:\Tmp\Kies.jpg"
fn(2) = "C:\Tmp\Holz.jpg" ' fn(2) equals fn(0)!

For k = 0 To 2

FileOpen (c1,fn(k),cRandom)
b = FileGetByte (c1,0)
FileClose (c1)


Image1.LoadPicture (fn(k))
Image1.Refresh
Sleep(1000)

Next k

At k = 2 (fn(0) = fn(2) !) the error "Can not access file because of usage by another process" occurs.
There is no error if you delete the either the FileOpen or the LoadPicture part.

How to solve this problem?

Horst

View attachment Test.sbp

View attachment 6289

View attachment 6290
 

agraham

Expert
Licensed User
Longtime User
It is a feature of .NET that while a Bitmap holds an image loaded from a file the file is locked. The help for my ImageLibEx library has a topic "Bitmap memory overview" that decribes some of the possible pitfalls when using Bitmaps. BitmapEx has a Clone method you can use to copy an image so you can dispose of the original and release the lock on the file.
 

Horst

Member
Licensed User
Longtime User
Hello Andrew,

thank you very much. Your hint was very helpful but unfortunately now I have the same problem using the DrawerEx object. Is there a similar method to release a file if it locked by the DrawerEx functions?

solidbrush.New1(cWhite)
normalfont.New1("Calibri",20,0

FileOpen (c1,fn(k),cRandom)
b = FileGetByte (c1,0)
FileClose (c1)

bmpex.New1(fn(k))

drawerex.New2 (bmpex.Value)
drawerex.Font = normalfont.value
drawerex.DrawString1 ("AAAAAA", solidbrush.Value, 10,10)

bmpex1.New2(800,600)
bmpex1.Value = bmpex.Value
Image1.Image = bmpex1.Value
Image1.Refresh
bmpex.Release

Without the DrawerEx functios the code runs without any problems.

Best regards
Horst
 

Attachments

  • Error.txt
    5.5 KB · Views: 216

agraham

Expert
Licensed User
Longtime User
Is there a similar method to release a file if it locked by the DrawerEx functions?
DrawerEx does not keep a file locked although the bitmap it is associated with might be doing so. The error in error.txt is not caused by a file being locked but appears to be an invalid parameter error but you don't tell me which line causes the error.

However this is not doing what you seem to think
B4X:
bmpex1.New2(800,600)
bmpex1.Value = bmpex.Value
...
bmpex.Release
The first line sets bmpex1.Value to a newly created blank bitmap sized 800 x 600. The second line immediately replaces that new bitmap with a reference to the existing bitmap in bmpex! Both bmpex1 and bmpex are now holding a reference to the same bitmap and the new 800 x 600 bitmap is now floating around unreferenced and waiting for the garbage collector to get rid of it and dispose the native bitmap it is holding. You then release the bitmap referenced by bmpex which therefore invalidates the bitmap referenced by Image1 which is probably where the fault occurs as Image1 tries to paint itself with an invalid bitmap. Remember bitmaps are passed around as reference objects. If you want to duplicate a bitmap you need to use Clone which makes a new copy of a bitmap.
 

Horst

Member
Licensed User
Longtime User
Hello Andrew,

I have simplified my code to show my problem somewhat better:



solidbrush.New1(cWhite)
normalfont.New1("Calibri",20,0)

FileCopy("C:\Tmp\Holz.jpg","C:\tmp\tmp.jpg")

bmpex.New1("C:\tmp\tmp.jpg")

drawerex.New2 (bmpex.Value)
drawerex.Font = normalfont.value
drawerex.DrawString1 ("AAAAAA", solidbrush.Value, 10,10)

Image1.Image = bmpex.Value
Image1.Refresh

bmpex.Release

FileDel ("C:\tmp\tmp.jpg")


There is an access error at the last line only when using the DrawerEx object. In

this case it it impossible too to delete the file by Windows tools.
That means the file keeps locked in spite of bmpex.Release, or not ?!
When I comment out the drawerex lines the file is released as expected.
On the other hand the code does what is intented by canceling FileDel.

Please could you correct the code above if there are mistakes?

Thank you in advance
Horst
 

agraham

Expert
Licensed User
Longtime User
Previously I noted
DrawerEx does not keep a file locked although the bitmap it is associated with might be doing so.
This is what is happening. DrawerEx is still associated with the bitmap in the file you want to delete.

In retrospect I should probably have given DrawerEx a method to dispose of its internal Graphics object (the .NET object that does the drawing) but you can achieve the same effect by using
B4X:
Drawerex.Dispose
AddObject("DrawerEx", "DrawerEx") ' add it back to use again
Your code still has a problem in that you are invalidating the bitmap that is referenced by Image1.Image. If you comment out Image1.Refresh you will see the dreaded red cross. Including Image1.Refresh draws the Image before disposing the bitmap but if Image1 ever has to repaint itself again you will get the red cross. A better way is to make a copy of the original bitmap as below. Clone does not work here as it seems it carries the bitmap locking across to the cloned bitmap. Note that I have changed the object names as it is not good practice, although possible, to name an object with the same name as its type.
B4X:
Solidbrush1.New1(cWhite)
Normalfont1.New1("Calibri",20,0) 

FileCopy(AppPath & "\test.jpg",AppPath & "\tmp.jpg")

BmpEx2.New1(AppPath & "\tmp.jpg")
BmpEx1.New2(Bmpex2.Width, BmpEx2.Height)

Drawerex1.New2(BmpEx1.Value)
RectEx1.New1(0, 0, Bmpex2.Width, BmpEx2.Height)
Drawerex1.DrawImage(BmpEx2.Value, RectEx1.Value, RectEx1.Value, False)
Drawerex1.Font = Normalfont1.value
Drawerex1.DrawString1("AAAAAA", Solidbrush1.Value, 10,10)
Image1.Image = BmpEx1.Value

BmpEx2.Release
FileDel (AppPath & "\tmp.jpg")
I am baffled that with my code the drawn string is the size I would expect given the size of the font but your code draws it hugely bigger. I am presently unable to explain this behaviour which looks like an anomalous effect within the .NET GDI+ code although I might be missing some obvious explanation.

@Klaus and any other power users of ImagelibEx. Have you seen any effect with DrawString like this before or can you see something obvious that I have overlooked.
 

klaus

Expert
Licensed User
Longtime User
Hi Andrew,
@Klaus and any other power users of ImagelibEx. Have you seen any effect with DrawString like this before or can you see something obvious that I have overlooked.

I have never noticed a problem like this.
Are you shure that, in Hort's program, the Image1 control's ImageMode is not set to cStrechImage, and in yours to cNormalImage or cCenterImage ?

Best regards.
 

agraham

Expert
Licensed User
Longtime User
I can reproduce the difference by commenting out a single line in the following code that should make no difference :confused:
B4X:
Solidbrush1.New1(cWhite)

FileCopy(AppPath & "\test.jpg",AppPath & "\tmp.jpg")

BmpEx2.New1(AppPath & "\tmp.jpg")
BmpEx1.New2(Bmpex2.Width, BmpEx2.Height)

[COLOR="Green"]' comment the following line out to see the difference[/COLOR]
[COLOR="Red"]BmpEx1.Value = BmpEx2.Clone [/COLOR]

Drawerex1.New2(BmpEx1.Value)
RectEx1.New1(0, 0, Bmpex2.Width, BmpEx2.Height)
Drawerex1.DrawImage(BmpEx2.Value, RectEx1.Value, RectEx1.Value, False)

Normalfont1.New1("Calibri",10,0) 
Drawerex1.Font = Normalfont1.value
Drawerex1.DrawString1("AAAAAA", Solidbrush1.Value, 0,0)

Image1.Image = BmpEx1.Value
 

agraham

Expert
Licensed User
Longtime User
Thanks Klaus, that gave me the clue I needed. I also see no difference in my code if I substitute your jpg for the one I'm using which I just chose because it was handy. If I use my jpg in your programs the effect occurs.

Then the penny dropped and I looked at the image data. The pixels per inch for my image were 300 and for your image 96. Fonts, as we know from AutoScale, are defined in points which are a physical size. What I didn't know is that DrawString when drawing on a bitmap uses the ppi of the bitmap to convert the points value to pixel values for drawing so I got large characters owing to the greater pixels per inch value of my bitmap. I get really confused with the different mappings in GDI :confused: I wish it would just work in either pixels or physical sizes and not let you mix them :(
 

klaus

Expert
Licensed User
Longtime User
I wasn't aware of the ppi value in images either.
The picture I included in my post is a part of a desktop screenshot I reduced to the PPC screen size to minimize the image size.

Tryed with another picture from my digital camera and get also the error.

I'm shure that all the images I used till now in my programs (at least the ones I write text on) were desktop screenshots.

Is there a mean to read the ppi value from the images to eventually adjust the font size.

Best regards.
 

Horst

Member
Licensed User
Longtime User
Hello Andrew,

thank you very for your patience! Now the coin dropped too for me. The key point was the usage of the AddObject method, which I never used before. Now only two lines of code solved my problem:
<AddObject("drawerex1", "DrawerEx")> before each drawing part and
<drawerex1.Dispose> behind. I am sure it is a better coding practice to do it with a second Bitmap, but in my case it seems to be sufficient to dispose the drawer. Thanks again!

Best regards
Horst
 

agraham

Expert
Licensed User
Longtime User
Is there a mean to read the ppi value from the images to eventually adjust the font size.
I've done some further testing and this "problem" only happens on the desktop. Device bitmaps always seem to have the ppi of the screen regardless of the setting in the file.

On the desktop bitmaps created by New2 seem to be created with the same ppi of the screen (always 96 for Windows as far as I know) so if you use DrawImage, like my demo of the effect, to copy all or part of a bitmap loaded from a file to a new bitmap you will know that DrawString will behave as expected.

Alternatively, and this only works on the desktop, using the Door library you can find the ppi of a bitmap. In practice it is almost certain that the horizontal and vertical resolution values will be the same.
B4X:
Obj1.New1(False)
Obj1.Value = BmpEx1.Value
msg = Obj1.GetProperty("HorizontalResolution")
msg = msg & " x " & Obj1.GetProperty("VerticalResolution")
Msgbox(msg)
 

Horst

Member
Licensed User
Longtime User
Hello Andrew and Klaus,

I had to draw text on a lot of pictures having very different ppi values (72 ...150). To get the ppi I did it this way:

normalfont1.New1("Courier",100,0)
...
drawerex1.New2 (bmpex1.Value)
drawerex1.Font = normalfont1.value
strheight = drawerex1.StringHeight("TEST")
ppi = 0.6 * 96 * strheight/100)
...
Fontsize = Fontsize0 * (96/ppi) * (bmpex1.Height/Image1.Height)
normalfont2.New1 ("Georgia",Fontsize,0)

With normalfont2 the labels of all images have the same size.

Best regards
Horst
 
Top