Bild mit über 10 GP

  • VB.NET

Es gibt 51 Antworten in diesem Thema. Der letzte Beitrag () ist von BjöNi.

    Bild mit über 10 GP

    Hallo,

    was für Möglichkeiten gibt es, eine Grafik mit über 10 Gigapixel zu erstellen (100.000px * 100.000px)? Eine Bitmap crasht ja schon bei weniger als einem Megapixel.
    Es geht mir erstmal nicht um die Geschwindigkeit, es geht mir darum, dass es geht.

    Wäre für Vorschläge dankbar.

    MfG, BjöNi ;)

    Artentus schrieb:

    Du könntest ein zweidimmensionales Color.Array erstellen, das 100.000 * 100.000 Elemente besitzt.
    Und wie kriege ich das hinterher als Bilddatei gespeichert?

    Artentus schrieb:

    Aber dir ist schon klar, dass das dann 40 Gigabyte groß wäre, oder?
    Ja :D

    BjöNi schrieb:

    Und wie kriege ich das hinterher als Bilddatei gespeichert?
    Als Bitmap (*.bmp - lineares Format) kannst Du Dir die Datei quasi offline auf der Platte zusammenbauen.
    Falls Du diesen Code kopierst, achte auf die C&P-Bremse.
    Jede einzelne Zeile Deines Programms, die Du nicht explizit getestet hast, ist falsch :!:
    Ein guter .NET-Snippetkonverter (der ist verfügbar).
    Programmierfragen über PN / Konversation werden ignoriert!
    Es ist unmöglich, das auf einmal anzuzeigen, ja. Aber es wäre doch möglich, eine unscharfe Übersicht anzuzeigen, und dann den entsprechenden Teil, der rangezoomt wird (So einen großen Bildschirm hat eh niemand), nachzuladen, oder? Bzw. wie läuft das denn bei "echten" Bildanzeigeprogrammen ab? Laden die das ganze Bild in den RAM oder nicht?

    @vb-checker: Ich kann es gerade nicht so recht reproduzieren, ich versuche das irgendwann später nochmal.
    OK... Dann würde ich sagen, ich ändere meine "Taktik" und speichere viele kleine Bilder, die dann eben nur in meinem Programm angezeigt werden können. Geht wahrscheinlich einfacher.
    Hi
    speichere eine große Datei so, dass du die einzelnen Segmente direkt laden kannst. Am besten wären halt Blöcke mit z.B. der Abmessung 32*32 px. Die kannst du dann bei Bedarf laden und, wieder freigeben, sobald sie nicht mehr benötigt werden und nicht mehr ausreichend Speicher verfügbar ist. Außerdem kannst du quasi Mipmaps auf diesen 32*32-Feldern generieren und ggf. als Kontextdaten mit abspeichern, um Zoomen flüssig zu ermöglichen. Mipmaps kannst du dann rekursiv generieren.

    Gruß
    ~blaze~
    Ich habe sie bereits als Einzelbilder mit 256*256px in einem Ordner. Das Anzeigen ist eiglich auch kein Problem, ich male halt per GDI+ auf die Form. Aber wenn ich weit wegzoome, kriege ich trotzdem ne OutOfMemoryException, weil natürlich trotzdem alle Bilder in voller Größe geladen werden. Muss ich dann also eine kleinskalierte Kopie des Bildes erstellen, und die vollgröße disposen? Oder wie kriege ich das am besten hin?
    Muss ich dann also eine kleinskalierte Kopie des Bildes erstellen, und die vollgröße disposen?
    So würde ich es zumindest zuerst probieren. Evtl bekommst du das auch ganz ohne zusätzliche Bitmap hin. In etwa so:

    VB.NET-Quellcode

    1. Using g as Graphics = Graphics.FromImage(bmp)
    2. g.DrawImage(bmp, New Rectangle(0, 0, neueBreite, neueHöhe), New Rectangle(0, 0, bmp.width, bmp.height), GraphicsUnit.Pixel)
    3. bmp = ctype(bmp.Clone(New Rectangle(0, 0, neueBreite, neueHöhe)), Bitmap)
    4. End Using

    Nur so theoretisch, habs nicht getestet.

    Ich kenne übrigens ein Spiel, dass zeigt eine Karte von ganz Europa an und hat auch stufenlosen Zoom. Dazu ist jeder Quadrant der Karte für 8 verschiedene Zoomstufen schon vorverkleinert. Es wird also meist die Bitmap geladen, die am nähesten an die gewollte Zoomstufe herankommt und nur auf der höchsten Zoomstufe wird tatsächlich die ganz große Bitmap geladen.

    Skybird schrieb:

    Das sind ja Ubisoftmethoden hier !

    Schreib' dir doch einfach ein eigenes Dateiformat, das mehrere Blöcke hat:
    Layout wäre Header, Frame*
    Header:
    - 'IDCF' whatever, irgendeine Identifikation halt (image data container format) : Int32
    - frame count : Int32

    Frame:
    - FrameLength : Int64
    - Quality : Int16 (je kleiner der Wert, desto höher die Reduktionsstufe, &HFFFF ist Maximum, Qualitätsabnahme ist exponentiell)
    - Width : Int32
    - Height : Int32
    - SegmentResolution (Größe eines einzelnen Segments ergibt sich zu SegmentResolution * SegmentResolution) : Int16
    (ggf.
    - BlockSize (Größe eines einzelnen Pixels in Bits) : In Int16
    - PixelFormat (Format der Pixel) : PixelFormat
    )
    - FrameData : byte[]

    Bei obigem Dateiaufbau hast du halt einen Header, der die Zahl der Bilder bereitstellt. Jeder hat eine bestimmte Qualität, die angibt, wie hoch die Auflösung ist. Es werden dann eben 2^(-quality) Pixel zu einem einzelnen zusammengefasst (einfach Farbwerte aufaddieren und durch Anzahl teilen, bei der Segmentierung rundest du zur nächsten Zweierpotenz auf, nimmst aber die Zahl der tatsächlich vorhandenen Pixel. Siehe unten). Das verlustfreie Bild hat die Qualität 0. So erzeugst du z.B. aus einem Bild mit 256x256 Pixeln bei einer Qualität von -1 ein Bild von 128x128 Pixel, wobei je 4 Pixel zu einem zusammengefasst werden, bei -2 von 64x64 und somit werden 16 Pixel zusammengefasst, bei -3 32x32, usw. Sinnvoll ist es dann nat., nicht -1, -2, -3 zu nehmen, sondern -2, -4, -8, -16 usw. ab einer bestimmten Größe ist's dann auch sinnvoll, die qualitativ geringwertige Darstellung zur Laufzeit zu berechnen - aus der mit der geringsten Qualität.
    Zum Laden erzeugst du viele Bitmaps mit den Abmessungen SegmentResolution x SegmentResolution. Nicht alle Bitmaps haben Abmessungen, die zweier-Potenzen sind. Somit gibt es auch leere bzw. unbeschriebene Abschnitte in der Bitmap. Die sind zu beachten (einfach leer mit abspeichern, aber bei der zu zeichnenden Width und Height beachten). In allen Frames wird segmentweise gespeichert, d.h. jedes Segment hat die Größe BlockSize * SegmentResolution * SegmentResolution. Aus Width ergibt sich die Zahl der Segmente (Width + (1 >> Quality) - 1) / (1 >> Quality) pro Zeile. Somit kannst du das Segment der Zelle (x, y) über die Stream-Position (x + y * (Width + (1 >> Quality) - 1) / (1 >> Quality))) * BlockSize * SegmentResolution * SegmetResolution + offset laden, wobei offset = FrameHeaderGröße + FramePosition. Die BlockSize wäre bei 32-Bit Argb eben 32, ich würde das an deiner stelle weglassen.
    Die Daten bekommst du in eine Bitmap, indem du sie an den entsprechenden Bitmap-Konstruktor übergibst oder LockBits angemessen verwendest. Ein Byte-Array enthält hierbei alle Daten für ein Segment. Das kannst du ja über GCHandle.Alloc anpinnen (gibt's eine Überladung mit ieinem Wert mit Namen Pinned oder so) und über AddrOfPinnedObject oder wie das heißt den Zeiger darauf abfragen und über das UserInputBuffer und ReadOnly in die Bitmap klatschen. Den Code dafür kann ich dir noch geben, wenn du ihn brauchst.
    Anschließend schreibst du eine Klasse, die einen bestimmten Ausschnitt lädt und nicht benötigte Ausschnitte löscht oder zum löschen freigibt. Den Fokus updatest du, sobald neue Daten benötigt werden.
    Ich hoffe mal, dass das jetzt nicht wieder zu viel ist (befürchte aber was anderes ;)). Was du hier speicherst, sind halt Rohdaten, keine z.B. PNGs, d.h. die Datei wird extrem aufgebläht. Was du machen kannst ist, dass du eine offset-Tabelle speicherst statt der Daten und die Bitmap-Daten einreihst. Um effizient auf einzelne Segmente zugreifen zu können, brauchst du halt eine einfache Formel, wie die Stream-Positions-Formel von oben.

    Gruß
    ~blaze~