Výřez z obrázku   zodpovězená otázka

VB.NET, WinForms, Grafika

Zdravím,

omlouvám se že zde zakládám asi zbytečné téma ale už opravdu nevím jak dál.

Jde o to, že mám PictureBox1 s velkým obrázkem (např. fotka) a potřebuju z něj udělat výřez o rozlišení 128x128pix. a ten vykreslit do PictureBoxu2. Zadám pozici výřezu (levý horní roh) a velikost a ono mi to vrátí obrázek. Pan Petr Mánek mi dal tenhle kód :

Public Function Vyrizni(ByVal pict As PictureBox) As Bitmap
    Dim b As Bitmap = pict.Image.Clone()
    Dim b2 As New Bitmap(128, 128)
    Dim g As Graphics = Graphics.FromImage(b2)

    g.DrawImageUnscaledAndClipped(b, New Rectangle(X, Y, 128, 128))
    g.Dispose()
    Return b2
End Function

Když zmněním hodnoty X a Y tak se neposune výřez, ale výsledný obrázek se v PictureBoxu2 ořízne.

Opravdu už nevím co dělám špatně. Jestli ten kód používám špatně, nebo ...

Moc děkuju za případné odpověďi.

nahlásit spamnahlásit spam 0 odpovědětodpovědět

Místo metody

g.DrawImageUnscaledAndClipped(b, New Rectangle(X, Y, 128, 128))

použijte

g.DrawImage(b, 0, 0, New Rectangle(X, Y, 128, 128), GraphicsUnit.Pixel)

U Vaší metody zadáváte obrázek pro vykreslení a pak obdélník DO kterého se má vykreslovat - proto se Vám to v tom druhém pictureboxu "ořezává", protože nekreslíte od počátku, ale někam dále.

nahlásit spamnahlásit spam 1 / 1 odpovědětodpovědět

Díky moc.

Až se dostanu na svůj počítač tak to zkusím.

Ještě jednou děkuji

nahlásit spamnahlásit spam 0 odpovědětodpovědět

Tak jsem to doma zkusil. Obrázek to vyřízne správně (díky). Ale pouze obrázky v rozlišení 96dpi. Obrázky které mají jiné dpi (např. 72dpi) na sebe nenavazují (když vyříznu dva vedle sebe). Nevíte ještě jestli se to dá nějak nastavit?

nahlásit spamnahlásit spam 0 odpovědětodpovědět

A jak ty dva výřezy "sešíváte"? do jednoho pictureboxu, nebo do dvou vedle sebe?

Ten výše popsaný výraz by měl dávat vždy stejně veliký obrázek (v pixelech). Zkuste si pohrát s nastavením "SIzeMode" jednotlivých pictureboxů.

Nemám s tím zkušenosti - byl jsem přesvědčen, že (alespoň) WinForm jede ve fyzických pixelech obrazovky a tam je obrázek 128 x 128 pixelů stejně velký, ať má DPI jakékoliv.

Je ale pravdou, že teď jsem se trochu díval na WPF a tam píšou něco o "bezrozměrných pixelech" tak, aby výstup byl nezávislý na rozlišení monitoru - a pak se asi počítá i s tím DPI.

Ale pokud to dáváte na winform, dáte si vedle sebe 2 pictureboxy stejné velikosti a do nich nacpete výřezy o velikosti 128 x 128 pixelů myslím, že by měly na sebe navazovat (být stejně veliké), ať je zdroj jakýkoliv (přinejhorším nastavte SizeMode u PictureBoxů na StretchImage.

ještě mě napadá jedna možnost - tam by mohl být pes zakopán - zkuste změnit jednotky v tom příkaze, který jsem Vám posledně radil (poslední parametr funkce) - Intellisense Vám napoví. Nevím už, co jsem Vám tam dal (nemám to teď před sebou), ale tam by mohl být problém.

nahlásit spamnahlásit spam 1 / 1 odpovědětodpovědět

Díky za odpověď. Ale nemusíte si s tím dělat starosti :)

Moje aplikace funguje tak, ze si otevřu nějakej obrázek (třeba fotku), ten to rozřeže na jednotlivý čtverečky (např. o rozlišení 128x128 pix. a každej zvlášť to uloží. Otevřel jsem fotku asi 2000x2000 pix. (72dpi) a nechal jsem ji rozřezat na 4 čtverce, každý o rozlišení 1000x1000 pixelů. Jako výstup byly opravdu 4 obrázky, každý v rozlišení 1000x1000 pix. (92dpi).

Ale když jsem se na ně podíval nenavazovali přesně na sebe ale mezi výřezy "chybělo" tak 200pixelů (pokud jsou výřezy menší chybí míň pixelů).

To samé jsem zkoušel s fotkou (stejné rozlišení, stejné výřezy) akorát jsem dpi přenastavil na 92. S tím to funguje bez chyby, obrázky na sebe přesně navazují.

Jednotky jsem zkoušel měnit (vyzkoušel jsem všechny). Bohužel všechny kromě "GraphicsUnit.Pixel" vyhodí chybu.

Zkoušel jsem u PictureBoxu ze kterého obrázek beru nastavit "ScaleMode" ale v VB.NET to asi není.

No každopádně to je zvláštní...

V každém případě díky za pomoc při programování.

PS.: Můžete mi (všichni) tykat, zas tak starej nejsem :)

nahlásit spamnahlásit spam 0 odpovědětodpovědět

Nevím, jak přesně vypadá tvůj kód, ale na DPI by to skutečně nemělo být závislé.

(jinak va vlastnost se nejmenuje ScaleMode, ale SizeMode)

Zkusil jsem jen tak narychlo nahodit tento kód:

Public Class Form1
    Private obraz As PictureBox
    Private dilky(3) As PictureBox


    Private Sub Form1_Load(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles MyBase.Load
        ' nastavíme hlavní PictureBox
        obraz = New PictureBox
        With obraz

            .Left = 0
            .Top = 0
            .Width = 500
            .Height = 500
            .BackColor = Color.White
            .BorderStyle = BorderStyle.FixedSingle
            .SizeMode = PictureBoxSizeMode.StretchImage
            .Image = Image.FromFile("c:\obr.jpg")
        End With
        Me.Controls.Add(obraz)

        ' vytvoříme 4 pictureboxy
        For i As Integer = 0 To 3
            dilky(i) = New PictureBox
            With dilky(i)
                ' v následujících dvou řádcích si můžeš dát pro lepší názornost místo hodnoty 250 hodnotu třeba 251
                .Left = (i Mod 2) * 250
                .Top = 550 + (i \ 2) * 250
                .Width = 250
                .Height = 250
                .SizeMode = PictureBoxSizeMode.StretchImage
            End With
            Me.Controls.Add(dilky(i))
        Next
        ' tato část (výše) je uvedena pouze pro ilustraci, můžeš si to vytvořit i v designeru
        ' naklikáním. Pro jednoduchost je to taky uděláno pro čtvercový obrázek a pro dělení 2x2
        ' asi zvládneš sám zobecnit (bohužel musím se teď vzdálit) pro obecný obrázek a obecné dělení



        ' tady naplníme dílčí pictureboxy kouskama obrázků
        For i As Integer = 0 To 3
            dilky(i).Image = vyrez(obraz.Image, 2, 2, i Mod 2, i \ 2)

        Next
    End Sub

    ''' <summary>
    ''' Vlastní funkce na rozstříhání obrázků
    ''' </summary>
    ''' <param name="zdroj">Vstupní bitmapa, kterou chceme rozstříhat</param>
    ''' <param name="sloupcu">Celkový počet sloupců</param>
    ''' <param name="radku">celkový počet řádků</param>
    ''' <param name="sloupec">kolikátý sloupec chceme získat (počítáno od 0!)</param>
    ''' <param name="radek">kolikátý řádek chceme získat (počítáno od 0!)</param>
    ''' <returns>vyříznutý obrázek</returns>
    ''' <remarks></remarks>
    Private Function vyrez(ByVal zdroj As Bitmap, ByVal sloupcu As Integer, ByVal radku As Integer, ByVal sloupec As Integer, ByVal radek As Integer) As Bitmap
        Dim sirkaVyrezu As Integer = zdroj.Width \ sloupcu
        Dim vyskaVyrezu As Integer = zdroj.Height \ radku
        Dim X As Integer = sloupec * sirkaVyrezu
        Dim Y As Integer = radek * vyskaVyrezu

        Return zdroj.Clone(New Rectangle(X, Y, sirkaVyrezu, vyskaVyrezu), zdroj.PixelFormat)


    End Function
End Class

a skutečně ať tam dám obrázek s libovolným DPI (zkoušel jsem tedy jen 2 možnosti, obrázek 2010 x 2010 pix při 96 a 72 dpi), výsledek je vždy stejný a korektní.

(v uvedeném kódu jsem, samozřejmě, neřešil ošetření chyb, no a pak je tam ještě jeden funkční nedostatek - pokud totiž není rozměr obrázku dělitelný počtem sloupců, pak ti budou na pravém a spodním okraji obrázku chybět nějaké ty pixely (ale pouze málo - u 2 sloupců max. 1 sloupec pixelů, u dělení na 3 sloupce max. 2 sloupce pixelů atd.)

Tento nedostatek si ale jistě již dokážeš odstranit sám.

P.S. Mně taky můžete tykat, ač už tak starej (bohužel) jsem :(

nahlásit spamnahlásit spam 1 / 1 odpovědětodpovědět

Tak jsem zkusil ten kód a opravdu funguje.

Tak jsem asi někde udělal chbu já (kdo jinej taky?) Můj kód vypadá asi takto:

Public Class Form1

    Public Function Vyrizni(ByVal Pict As PictureBox, ByVal X As Integer, ByVal Y As Integer) As Bitmap
        Dim b As Bitmap = Pict.Image.Clone()
        Dim b2 As New Bitmap(320, 240)
        Dim g As Graphics = Graphics.FromImage(b2)

        g.DrawImage(b, 0, 0, New Rectangle(X, Y, 320, 240), GraphicsUnit.Pixel)
        g.Dispose()
        Return b2
    End Function


    Private Sub Button1_Click(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles Button1.Click
        PictureBox2.Image = Vyrizni(PictureBox1, 0, 0)
        PictureBox3.Image = Vyrizni(PictureBox1, 0, 240)
    End Sub
End Class

Stačí na form dát 3 PictureBoxy a Button. Do PictureBoxu1 vložit obrázek a kliknout na tlačítko. V PictureBoxu 1 i 2 se vytvoří výřezy o rozlišení 320x240 pixelů.

Pokud se chceš podívat na screenshoty (s obrázky s 72 i 96 dpi), tak se koukni sem:

http://mylms.ic.cz/pic/extern_72dpi.jpg

http://mylms.ic.cz/pic/extern_96dpi.jpg

Kód je úplně stejnej (viz. nahoře) akorát je vložen obrázek s rozdílným dpi. Vlevo je původní obrázek a vpravo výřezy. Stejný obrázky jsem použil i v tvým kódu a tam to šlo.

Takže vlastně i kdybych udělal chybu tak by obrázky měli vypadat stejně, jenže v závislosti na dpi obrázku jsou deformovaný.

PS.: U Všech PictureBoxů jsem zkoušel nastavit SizeMode na Normal, AutoSize a StretchImage a výsledek byl vždy stejnej...

PPS.: Určitě se bývalý ScaleMode jmenuje teď SizeMode? Ve VB6 se dal u PictureBoxu nastavit parametr ScaleMode na: User, Twip, Point, Pixel, Character atd. Tak nevim...

nahlásit spamnahlásit spam 0 odpovědětodpovědět

S výsledkem máš pravdu. Skutečně se to tak chová.

Hledal jsem, hledal, a zjistil jsem věc, kterou jsem netušil - při kreslení na graphics se bere v potaz i rozlišení. (dpi)

No a při vytvoření obrázku (bez nějakého prototypu) tak, jak tvoříš příkazem

 Dim b2 As New Bitmap(320, 240)

se obrázku automaticky nastaví standardní rozlišovačka pro monitory, tzn. 96 DPi.

No a pokud jsem si natáhnul obrázek s 300 dpi, tak Bitmapa b2 sice zůstane v původním rozměru (320 x 240 bodů) a vyplní tak celý příslušný PictureBox, jenomže při tom vykreslování na graphics

g.DrawImage(....

je graphics tak chytrý (nebo blbý, jak libo), že usoudí:

Mám obrázek 300 (pro jednoduchost) pixelů široký, ale pochází ze zařízení, kde byla rozlišovačka 300 dpi, tedy fyzicky (při pozorování) měřil 1 palec na šířku.

Nyní tento obrázek potřebuji vykreslit na zařízení, které umí pouze 96 dpi, tedy kdybych ho vykreslil stylem pixel = pixel, pak by fyzicky měřil 3,125 palců na šířku. Aby ale vypadal jako původně (šířka 1 palec), musím ho přepočítat z 300 na 96 pixelů na šířku.

No a takto se nám nakreslí do levého horního rohu bitmapy b2 a zbytek bitmapy zůstane nevybarvený.

Řešení:

Jistě více, nalezl jsem 2 (mimo ten kód z předešlého příspěvku, který je jednodušší):

1) nastavit i u bitmapy b2 stejnou rozlišovačku, jakou má vstupní obrázek, třeba takto:

Dim b2 As New Bitmap(320, 240)
b2.SetResolution(b.HorizontalResolution, b.VerticalResolution)

Výsledek - dostaneš správné kousky obrázku, které budou mít nastavenou i rozlišovačku (dpi) stejnou, jaká je u zdrojového obrázku

2) vykašlat se na inteligenci graphics a natvrdo mu říci, na jak velkou část bitmapy (b2) má ten výřez nakreslit (samozřejmě mu zadáme, ať kreslí na celé b2). Třeba tak, že použiješ jiné přetížení metody DrawImage:

g.DrawImage(b, New Rectangle(0, 0, 320, 240), New Rectangle(X, Y, 320, 240), GraphicsUnit.Pixel)
'                |-> cílový obdélník, kam to má roztáhnout (na celý b2)
'                                              |-> zdrojový obdélník - výřez CO má vykreslit

Dostaneš opět správné výřezy, tentokrát ale budou mít standardní rozlišovačku (tedy 96 dpi pro monitor) bez ohledu na rozlišovačku původního obrázku

nahlásit spamnahlásit spam 2 / 2 odpovědětodpovědět

Já si říkal že to bude mít společnýho s rozlišením (dpi) monitoru - někde jsem to viděl v nastavení monitoru (grafiky, nebo někde tam u zobrazení...)

Jak jde vidět i ze začátku zapeklitá otázka má své řešení. Budu muset ještě "ujít pořádnej kus cesty" abych byl programátor jak ty :)

nahlásit spamnahlásit spam 0 odpovědětodpovědět

S tím "programátorem" jsi mne docela pobavil :-)), ale zase na druhou stranu Tě musím ubezpečit, že jsi na správné cestě (přesněji na správných stránkách), protože to, co jsem již stačil o VB.NET pochytit, jsem pochytil hlavně na tomto webu, a to se tomu, bohužel, nemohu věnovat nějak uceleněji, spíše jenom příležitostně.

nahlásit spamnahlásit spam 0 odpovědětodpovědět

To mi povídej, jsem 12hodin v práci a když si najdu chvilku na programování tak už je zase večer a je čas spát protože druhej den jdu zase do práce :-|

Tenhle web je super. Je dobrý vědět že člověk najde víceméně ucelený informace o VB.NET. A že nemusí procházet "tisíc" stran než něco zjistí, nebo se dohledá odpověďi.

nahlásit spamnahlásit spam 0 odpovědětodpovědět
                       
Nadpis:
Antispam: Komu se občas házejí perly?
Příspěvek bude publikován pod identitou   anonym.
  • Administrátoři si vyhrazují právo komentáře upravovat či mazat bez udání důvodu.
    Mazány budou zejména komentáře obsahující vulgarity nebo porušující pravidla publikování.
  • Pokud nejste zaregistrováni, Vaše IP adresa bude zveřejněna. Pokud s tímto nesouhlasíte, příspěvek neodesílejte.

přihlásit pomocí externího účtu

přihlásit pomocí jména a hesla

Uživatel:  
Heslo:  

zapomenuté heslo

 

založit nový uživatelský účet

zaregistrujte se

 
zavřít

Nahlásit spam

Opravdu chcete tento příspěvek nahlásit pro porušování pravidel fóra?

Nahlásit Zrušit

Chyba

zavřít

feedback