2D-Grafik g2.drawImage() langsam

Planich

Aktives Mitglied
Ich hab das mal geändert wie du gesagt hast. Und was soll ich sagen. Er lädt es rein. Das Bild ist komplett einfarbig grau. Wenn ich dann an den Reglern drehe färbt sich das Bild langsam. Ich denke mal es werden die Farbwerte völlig falsch interpretiert. Das ist ja auch der Grund für meine komischen Berechnungen in meiner Methode. Weil eben nicht von schwarz nach weiß-32767 -> 32768

Java:
private static RenderedImage obtainImageWithShortBufferFromSomewhere() throws IOException 
    {
        
            String args = new String ("tiff16bit.tiff");
        
        
        TIFFDecodeParam param =null;    //TIFF
        //Tiff Decoder erstellen
        ImageDecoder dectiff=ImageCodec.createImageDecoder("TIFF", new File(args), param);
        //Rendered Image erstellen
        RenderedImage reIm =dectiff.decodeAsRenderedImage();
        return reIm;
    }
 
S

Spacerat

Gast
Das Ganze ist eigentlich recht einfach... [c]myBI.getRaster().getDataBuffer()[/c] und "Darunterliegende" Methoden sind der Grund, warum ein BufferedImage langsam wird, weil der DataBuffer damit desynchronisiert wird. Deswegen hantiere ich mit dem Raster und dem ColorModel.

Beim Ersetzen meiner Laderoutine durch deine, hast du schlicht vergessen, "originalImage" zu instanzieren und so kann auch kein Bild in OwImage gezeichnet werden und es bleibt Schwarz. Weis nicht, ob sich RenderedImages ebenso in einen Graphics-Kontext zeichnen lassen, deshalb einfach mal aus "originalImage" im Loader und "original" im OwnImage ein RenderedImage-Objekt machen (Klassennamen ersetzen).

ColorModel, SampleModel und Raster sind auch recht einfach erklärt. Ein Bild hat grundsätzlich einen Datenpuffer mit Pixelinformationen und ein Farbmodell mit Farbinformationen. Hier nicht erwähnenswert jedoch stets vorhanden, das SampleModel welches Aufschluss über die Organisation des Pixel- bzw. Datenpuffers liefert (z.B. alle Farbkomponenten in Blöcken getrennt oder in ordentlicher Reihe in einem Block). Grundsätzlich kann man zwischen zwei Bildarten unterscheiden

1. Farbindiziert:
Der Datenpuffer liefert Indices auf Farbwerte einer Palette (IndexedColorModel).
2. PixelSized:
Der Datenpuffer liefert konkrete Farbwerte welche aber per Farbmodell (DirectColorModel) definiert sein müssen.

Das Raster verbindet nun Sample- und ColorModel sowie einen Datenpuffer zu einem gültigen Bild. Über dieses Raster lässt sich das Bild "verifiziert" manipulieren. Holt man sich aber den Datenpuffer, könnte man dort direkt unverifizierte und damit ungültige Werte reinschreiben und bei einem Repaint müsste stets der komplette Puffer erneut verifiziert werden.
Solange ein solcher Puffer also gültig sein kann, kann Java ihn z.B. auch in einem beschleunigten Puffer auf der GraKa halten bzw. einen Hardware- und einen Softwarepuffer gleichzeitig verwalten. Macht man sich aber einen solchen Puffer irgendwie in Java verfügbar, ist's aus mit der Synchronisation, selbiges gilt auch für die Performance.
Sei nur noch zu erwähnen, dass ein solcher Hardwarepuffer stets mit (gültigen) konkreten Farbwerten beschrieben wird, es also ein PixelSizedBuffer ist.
 
Zuletzt bearbeitet von einem Moderator:
S

Spacerat

Gast
Eigentlich EDIT! Wenn's noch möglich gewesen wär'
Es ist natürlich nicht das Raster was die drei anderen Klassen zu einem gültigen Bild zusammenfügt, sondern Das BufferedImage selbst. Das Raster verbindet lediglich ein SampleModel und einen DatenPuffer. Das ändert aber lange nicht den Umstand, dass die Daten des Puffers nur durch das Raster verifiziert manipuliert werden können, wohl aber etwas daran, dass ein Raster nicht zwangsläufig die Farbwerte eines Bildes verwaltet.
@Marco13: BTW.: Diese Erkenntnis bewegt mich gerade dazu, das Konzept meines DirectBufferedImages noch einmal umzuwerfen. Der Benutzer wird schlicht daran gehindert, sich den synchronisierten DataBuffer zu holen, er bekommt lediglich den DirectBuffer.
 
Zuletzt bearbeitet von einem Moderator:

Marco13

Top Contributor
Ich denke mal es werden die Farbwerte völlig falsch interpretiert. Das ist ja auch der Grund für meine komischen Berechnungen in meiner Methode. Weil eben nicht von schwarz nach weiß-32767 -> 32768

Liegt das jetzt nur noch an der Konvertierung wie sie im Moment in convertShortToByteArray gemacht wird? Diese eine Methode könnte man eben durch eine andere ersetzen (deine, mit ug und og oder so...)
 

Planich

Aktives Mitglied
Beim Ersetzen meiner Laderoutine durch deine, hast du schlicht vergessen, "originalImage" zu instanzieren und so kann auch kein Bild in OwImage gezeichnet werden und es bleibt Schwarz. Weis nicht, ob sich RenderedImages ebenso in einen Graphics-Kontext zeichnen lassen, deshalb einfach mal aus "originalImage" im Loader und "original" im OwnImage ein RenderedImage-Objekt machen (Klassennamen ersetzen).
Ein RenderedImage kann man nicht in einen graphics Kontext zeichnen.
Und das instanzieren von "originalImage" kriege ich nicht hin. Ich nehme mal an das muss ich an der Stelle selbst implementieren aus den Informationen die ich durch das erstellte RenderedImage kriege. Ganz ehrlich: ich find das sehr kompliziert.

Deine Ausführungen zum Thema Puffer, ColorModel, Raster waren gut, aber der Groschen ist noch nicht so gefallen, dass ich das jetzt selbst implementiert kriege, aber ich versuchs mal.

Vielleicht hast du ja Zeit und Lust. Für dich sind das bestimmt blos 2min :(
 

Planich

Aktives Mitglied
Kann mir bitte nochmal jemand das "|" Zeichen in diesem Kontext erklären

Java:
for (int i = 0; i < buffer.getData().length / 3; i++) {
                raw[i] = 0xFF000000
                        | ((buffer.getData()[i + 0] & 0xFF) << 16)
                        | ((buffer.getData()[i + 1] & 0xFF) << 8)
                        | ((buffer.getData()[i + 2] & 0xFF));
            }

oder
Java:
byte input = byte[i];
int a = 0xFF000000;
int r = (input << 16);
int g = (input << 8);
int b = (input << 0);
int argb =  a | r | g | b;

ich kenne das "|" nur als oder und ich komme gerade nicht weiter ohne das mal langsam richtig verstanden zu haben
 

bERt0r

Top Contributor
Warum machst du's dir nicht einfacher:
Du lädst dein RenderedImage, wandelst es in ein BufferedImage um (du hast die Methode ja schon dafür), dann machst du aus dem BufferedImage ein TYPE_BYTE_GREY BufferedImage.
Wenn du jetzt mit deinen ober und untergrenzen herumhantierst, baust du dir aus diesem TYPE_BYTE_GREY BufferedImage ein neues, welches du anzeigst.
Wenn dein Bild nur grau sein soll kannst du dir die Umrechnungen alle sparen, dann brauchst du kein INT_RGB.
 

Marco13

Top Contributor
Erstaunlicherweise liefert Google dazu Ergebnisse, wenn auch etwas versteckt. Und sowas wie http://www.java-forum.org/java-basics-anfaenger-themen/133856-tut-operator.html kommt auch gelegentlich.

In diesem Fall: Die Bits der Werte werden "kombiniert":
Code:
  0x0000FF00 = Grün
| 0x00FF0000 = Rot
= 0x00FFFF00 = Gelb

BTW: Dein ursprüngliches [c] (buffer.getData()[i + 0] [/c] hatte mich irritiert, weil das aussah, als würden da DREI Helligkeitswerte (die jewels in einem byte stehen) zu EINEM Pixel (aus RGBA-Werten) kombiniert....
 
Zuletzt bearbeitet:
S

Spacerat

Gast
Das originalImage sollte sich aus dem RenderedImage so formen lassen:
Java:
WritableRaster wr = ri.getData().createCompatibleWritableRaster();
ColorModel cm = ri.getColorModel();
originalImage = new BufferedImage(cm, ri.copyData(wr), cm.isAlphaPremultiplied(), null);
Implementiert habe ich es ja bereits, mein Beispiel läuft bei mir ja. Verlinke mal die Grafik um die es geht und ich schau mal, was sich machen lässt.
Der |-Operator ist ein bitweises Oder, also sprich der Oder-Operator. Nicht zu verwechseln mit der boolschen Oder-Verknüpfung (||).
 
Zuletzt bearbeitet von einem Moderator:

Planich

Aktives Mitglied
Warum machst du's dir nicht einfacher:
Du lädst dein RenderedImage, wandelst es in ein BufferedImage um (du hast die Methode ja schon dafür), dann machst du aus dem BufferedImage ein TYPE_BYTE_GREY BufferedImage.
Wenn du jetzt mit deinen ober und untergrenzen herumhantierst, baust du dir aus diesem TYPE_BYTE_GREY BufferedImage ein neues, welches du anzeigst.
Wenn dein Bild nur grau sein soll kannst du dir die Umrechnungen alle sparen, dann brauchst du kein INT_RGB.

Dann ist das ganze doch wieder Type 0 und sau langsam beim Zeichnen oder nicht?

Und wieso soll ich wenn ich das TYPE_BYTE_GREY BufferedImage erstellt habe nochmal ein neues zum Anzeigen erstellen?

Edit: für alle mal ein kleines File zum Ausprobieren
tiff16bit2.png
 
Zuletzt bearbeitet:
S

Spacerat

Gast
BTW.: Das es langsam ist liegt definitiv nicht an TYPE_CUSTOM sondern ausschliesslich daran, dass du dir den DataBuffer des Rasters holst. Ich glaub' ich muss mal Zeichnung machen. Aber erst nach der Spätschicht. ;)
Danke für's Bild... hoff' das klappt auch mit png's. Ich werd' mein KSKB dann heute abend mal so umgestalten, dass es mit deinem Bild funktioniert und dass man dafür keine externen loader braucht. Dank "png" sollte das ja das geringste Problem sein.
 
Zuletzt bearbeitet von einem Moderator:

Planich

Aktives Mitglied
BTW.: Das es langsam ist liegt definitiv nicht an TYPE_CUSTOM sondern ausschliesslich daran, dass du dir den DataBuffer des Rasters holst. Ich glaub' ich muss mal Zeichnung machen. Aber erst nach der Spätschicht. ;)

wieso hab ich dann mit typ 2 eine wesentlich höhere Geschwindigkeit als mit TYPE_CUSTOM? Das Raster hole ich mir so oder so. (ausgehend von meinem Stand vor ein paar Tagen - mittlerweile haut ja gar nix mehr hin :lach)
 

Planich

Aktives Mitglied
Ich hab doch gleich zu Anfang gesagt, dass ich das Bild von dem ich mir den Buffer hole, nicht anzeige, sondern nur Bilder, die ich mit diesem Buffer erstelle

Edit: @Spacerat

hier mal dein Code von mir angepasst, ich hoffe so wie du meinst. Man muss auf jeden Fall den linken Slider sehr weit hoch ziehen, sonst bleibt das Bild komplett weiß oder schwarz. Das Bild an sich ist sehr stumpf, dunkel und kontrastarm

Java:
/*
 * To change this template, choose Tools | Templates
 * and open the template in the editor.
 */
package spacerat;

import com.sun.media.jai.codec.ImageCodec;
import com.sun.media.jai.codec.ImageDecoder;
import com.sun.media.jai.codec.TIFFDecodeParam;
import java.awt.BorderLayout;
import java.awt.Container;
import java.awt.Dimension;
import java.awt.Graphics;
import java.awt.Point;
import java.awt.image.BufferedImage;
import java.awt.image.ColorModel;
import java.awt.image.DataBuffer;
import java.awt.image.ImageObserver;
import java.awt.image.Raster;
import java.awt.image.WritableRaster;
import java.io.File;
import java.io.FileNotFoundException;
 
import javax.swing.JComponent;
import javax.swing.JFrame;
import javax.swing.JSlider;
import javax.swing.event.ChangeEvent;
import javax.swing.event.ChangeListener;
 

import java.awt.image.*;
import java.io.IOException;
 
 
public class OwnPanel
extends JComponent
{
    private static final long serialVersionUID = 1L;
 
    private final OwnImage img;
    private final ImageObserver obs = new ImageObserver()
    {
        @Override
        public boolean imageUpdate(java.awt.Image img, int infoflags, int x, int y, int width, int height)
        {
            repaint();
            return false;
        }
    };
 
    private OwnPanel(OwnImage img)
    {
        super();
        if(img == null) {
            throw new NullPointerException("img may not be null");
        }
        this.img = img;
        Dimension size = new Dimension(img.getWidth(), img.getHeight());
        setMinimumSize(new Dimension(1, 1));
        setPreferredSize(size);
        setSize(size);
    }
 
    @Override
    protected void paintComponent(Graphics g)
    {
        super.paintComponent(g);
        g.drawImage(img, 0, 0, getWidth(), getHeight(), obs);
    }
 
    public static void main(String[] args) throws IOException
    {
        if(args == null || args.length == 0) {
            args = new String[] {"tiff16bit.tiff"};
        }
        JFrame f = new JFrame("OwnImageTest");
        f.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
        Container cp = f.getContentPane();
        final JSlider upper = new JSlider(JSlider.VERTICAL);
        final JSlider lower = new JSlider(JSlider.VERTICAL);
        final OwnPanel op = new OwnPanel(new OwnImage(new File(args[0]).getAbsoluteFile()));
        cp.setLayout(new BorderLayout());
        cp.add(op, BorderLayout.CENTER);
        cp.add(upper, BorderLayout.WEST);
        cp.add(lower, BorderLayout.EAST);
        upper.setMaximum(0xFFFFFF);
        upper.setMinimum(0);
        upper.addChangeListener(new ChangeListener()
        {
            @Override
            public void stateChanged(ChangeEvent e)
            {
                op.img.setOgDraw(upper.getValue());
                op.repaint();
            }
        });
        lower.setMaximum(0xFFFFFF);
        lower.setMinimum(0);
        lower.addChangeListener(new ChangeListener()
        {
            @Override
            public void stateChanged(ChangeEvent e)
            {
                op.img.setUgDraw(lower.getValue());
                op.repaint();
            }
        });
        f.pack();
        f.setVisible(true);
    }
}
 
class OwnImage
extends BufferedImage
{
    private final BufferedImage original;
 
    public OwnImage(File f) throws IOException
    {
        this(new Loader(f));
    }
 
    private OwnImage(Loader l)
    {
        super(l.cm, l.data, false, null);
        original = l.originalImage;
        getGraphics().drawImage(l.originalImage, 0, 0, null);
    }
 
    public synchronized void setUgDraw(int ug)
    {
        ((OwnColorModel) getColorModel()).setUg(ug);
        getGraphics().drawImage(original, 0, 0, null);
    }
 
    public synchronized void setOgDraw(int og)
    {
        ((OwnColorModel) getColorModel()).setOg(og);
        getGraphics().drawImage(original, 0, 0, null);
    }
 
    public synchronized void setUgOgDraw(int ug, int og)
    {
        ((OwnColorModel) getColorModel()).setBorders(ug, og);
        getGraphics().drawImage(original, 0, 0, null);
    }
 
    private static class Loader
    {
        private ColorModel cm;
        private WritableRaster data;
        private BufferedImage originalImage;
        private RenderedImage riTiff;
        private Loader(File f) throws IOException
        {
            try {
            TIFFDecodeParam param =null;   
            //Tiff Decoder erstellen
            ImageDecoder decTiff=ImageCodec.createImageDecoder("TIFF", f, param);
            //Rendered Image erstellen
            riTiff=decTiff.decodeAsRenderedImage();
            } catch(FileNotFoundException e) {
                e.printStackTrace();
                return;
            }            
            int min = Integer.MAX_VALUE;
            int max = Integer.MIN_VALUE;
            
            Raster r = riTiff.getData();
            
            Object buffer = null;
            
            
            switch(r.getTransferType()) {
            case DataBuffer.TYPE_BYTE:
                buffer = new byte[r.getNumDataElements()];
                System.out.println("Byte");
                break;
            case DataBuffer.TYPE_SHORT:
            case DataBuffer.TYPE_USHORT:
                buffer = new short[r.getNumDataElements()];
                System.out.println("UShort");
                break;
            case DataBuffer.TYPE_INT:
                buffer = new int[r.getNumDataElements()];
                System.out.println("Int");
                break;
            default:
                throw new IllegalArgumentException("invalid type");
                
            }
            
            data = riTiff.getData().createCompatibleWritableRaster();
            cm = riTiff.getColorModel();
            originalImage = new BufferedImage(cm, riTiff.copyData(data), cm.isAlphaPremultiplied(), null);
            
            
            
            int w = r.getWidth();
            int h = r.getHeight();
            
            //C = aktueller Farbwert für den Pixel
            //max und min rausfinden
            int c = max;
            for(int x = 0; x < w; x++) {
                for(int y = 0; y < h; y++) {
                    c = cm.getRGB(r.getDataElements(x, y, buffer)) & 0xFFFFFF;
                    if(min > c) {
                        min = c;
                    }
                    if(max < c) {
                        max = c;
                    }
                }
            }
            data = Raster.createInterleavedRaster(DataBuffer.TYPE_BYTE, w, h, w, 1, new int[] {1}, new Point());
            cm = new OwnColorModel(min, max);
        }
    }
 
    private static class OwnColorModel
    extends ColorModel
    {
        private int og, ug;
        private double ratio;
 
        public OwnColorModel(int ug, int og)
        {
            super(8);
            setBorders(ug, og);
        }
 
        @Override
        public boolean isCompatibleRaster(Raster raster)
        {
            return true;
        }
 
        @Override
        public int getRed(int pixel)
        {
            return pixel & 0xFF;
        }
 
        @Override
        public int getGreen(int pixel)
        {
            return pixel & 0xFF;
        }
 
        @Override
        public int getBlue(int pixel)
        {
            return pixel & 0xFF;
        }
 
        @Override
        public int getAlpha(int pixel)
        {
            return 0xFF;
        }
 
        @Override
        public Object getDataElements(int rgb, Object pixel)
        {
            rgb = calcColor(rgb);
            if(pixel == null) {
                pixel = new byte[1];
            }
            if(pixel instanceof byte[]) {
                ((byte[]) pixel)[0] = (byte) (rgb & 0xFF);
            } else if(pixel instanceof short[]) {
                ((short[]) pixel)[0] = (short) (rgb & 0xFFFF);
            } else if(pixel instanceof int[]) {
                ((int[]) pixel)[0] = rgb;
            }
            return pixel;
        }
 
        private void setUg(int ug)
        {
            setBorders(ug, og);
        }
 
        private void setOg(int og)
        {
            setBorders(ug, og);
        }
 
        private void setBorders(int ug, int og)
        {
            this.ug = Math.min(ug, og);
            this.og = Math.max(ug, og);
            ratio = this.og - this.ug;
        }
 
        /**
         * Im Parameter "pixel" wird bei Bildern die nicht farbindiziert sind, wie in
         * diesem Fall, direkt der Farbwert übergeben.
         * Abhängig vom Typ des Quellbildes kann daraus der Bytewert wie im Beispiel
         * errechnet werden.
         * Das Beispiel ist äquivalent zu deinem geposteten Code.
         * In dieser Version wird "calcColor()" noch 4 mal pro Pixel aufgerufen. Ob sich
         * das ändern lässt, weis ich bisher noch nicht (liegt am Graphics-Object).
         * @param pixel
         * @return
         */
        private final int calcColor(int pixel)
        {
            int rc;
            int alpha = pixel & 0xFF000000;
            pixel &= 0xFFFFFF;
            if(pixel >= ug && pixel <= og) {
                rc = (int) (((pixel - ug) / ratio) * 255.0);
            }
            else if(pixel < ug) {
                rc = 0;//schwarz
            }
            else {
                rc = 0xFF;//weiß  
            }
            return alpha | rc << 16 | rc << 8 | rc;
        }
    }
}

Edit: Achja, es ist grottenlangsam :D
 
Zuletzt bearbeitet:

bERt0r

Top Contributor
Was ist grottenlangsam, das Zeichnen oder deine Berechnungen. Ich tippe mal sehr auf Zweiteres. Wenn du das die ganzen change events abfängst und nur dann neu berechnest, wenn der user den Balken auch loslässt, geht das auf einmal ganz fix:
Java:
import java.awt.BorderLayout;
import java.awt.EventQueue;
import java.awt.Graphics;
import java.awt.GridLayout;
import java.awt.Image;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.awt.event.AdjustmentEvent;
import java.awt.event.AdjustmentListener;
import java.awt.image.BufferedImage;
import java.io.IOException;
import java.lang.reflect.Field;

import javax.imageio.ImageIO;
import javax.swing.ImageIcon;
import javax.swing.JButton;
import javax.swing.JFileChooser;
import javax.swing.JFrame;
import javax.swing.JLabel;
import javax.swing.JPanel;
import javax.swing.JScrollBar;
import javax.swing.JScrollPane;
import javax.swing.JSplitPane;
import javax.swing.SwingConstants;
import javax.swing.border.EmptyBorder;

public class ImageBsp extends JFrame
{
	int upperBound=255, lowerBound;
	private JPanel contentPane;
	private JFileChooser fileChooser;
	private JLabel lblOriginal;
	private JPanel panel;
	private JLabel lblGrey;
	private JScrollPane panel_1;
	private JScrollPane panel_3;
	private BufferedImage imgOriginal, imgByteGrey, imgProcessed;
	private JLabel lblType;
	private JScrollPane scrollPane;
	private JLabel lblProcType;
	private JLabel lblProcessed;
	private JPanel panel_2;
	private JSplitPane splitPane;
	private JScrollBar upperBar;
	private JScrollBar lowerBar;
	
	/**
	 * Launch the application.
	 */
	public static void main(String[] args)
	{
		EventQueue.invokeLater(new Runnable()
			{
				public void run()
				{
					try
					{
						ImageBsp frame = new ImageBsp();
						frame.setVisible(true);
					} catch (Exception e)
					{
						e.printStackTrace();
					}
				}
			});
	}
	
	/**
	 * Create the frame.
	 */
	public ImageBsp()
	{
		setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
		setBounds(100, 100, 600, 400);
		contentPane = new JPanel();
		contentPane.setBorder(new EmptyBorder(5, 5, 5, 5));
		contentPane.setLayout(new BorderLayout(0, 0));
		setContentPane(contentPane);
		
		JButton btnLoadImage = new JButton("Load Image");
		contentPane.add(btnLoadImage, BorderLayout.SOUTH);
		
		panel_2 = new JPanel();
		contentPane.add(panel_2, BorderLayout.EAST);
		
		upperBar = new JScrollBar();
		upperBar.setVisibleAmount(1);
		upperBar.setMaximum(255);
		upperBar.setValue(255);
		upperBar.addAdjustmentListener(new AdjustmentListener() {
			public void adjustmentValueChanged(AdjustmentEvent e) 
			{
				if(!e.getValueIsAdjusting())
				{
					upperBound = upperBar.getValue();
					updateImage();
				}
			}
		});
		panel_2.setLayout(new GridLayout(0, 2, 0, 0));
		panel_2.add(upperBar);
		
		lowerBar = new JScrollBar();
		lowerBar.setVisibleAmount(1);
		lowerBar.addAdjustmentListener(new AdjustmentListener() {
			public void adjustmentValueChanged(AdjustmentEvent e) 
			{
				if(!e.getValueIsAdjusting())
				{
					lowerBound = lowerBar.getValue();
					updateImage();
				}
			}
		});
		lowerBar.setMaximum(255);
		panel_2.add(lowerBar);
		
		splitPane = new JSplitPane();
		splitPane.setResizeWeight(0.5);
		contentPane.add(splitPane, BorderLayout.CENTER);
		
		scrollPane = new JScrollPane();
		splitPane.setRightComponent(scrollPane);
		
		lblProcType = new JLabel("Bild mit Ober-Ug");
		lblProcType.setHorizontalAlignment(SwingConstants.CENTER);
		scrollPane.setColumnHeaderView(lblProcType);
		
		lblProcessed = new JLabel()
		{
			@Override
			public void paintComponent(Graphics g)
			{
				long start = System.nanoTime();
				super.paintComponent(g);
				long end = System.nanoTime();
				System.out.println("Time to paint: " + (end - start) + " ns");
			}
		};;
		lblProcessed.setHorizontalAlignment(SwingConstants.CENTER);
		scrollPane.setViewportView(lblProcessed);
		
		panel = new JPanel();
		splitPane.setLeftComponent(panel);
		panel.setLayout(new GridLayout(0, 2, 0, 0));
		
		panel_1 = new JScrollPane();
		panel.add(panel_1);
		
		lblOriginal = new JLabel();
		lblOriginal.setHorizontalAlignment(SwingConstants.CENTER);
		lblType = new JLabel("Original");
		lblType.setHorizontalAlignment(SwingConstants.CENTER);
		panel_1.setColumnHeaderView(lblType);
		panel_1.setViewportView(lblOriginal);
		
		panel_3 = new JScrollPane();
		panel.add(panel_3);
		
		lblGrey = new JLabel();
		
		lblGrey.setHorizontalAlignment(SwingConstants.CENTER);
		JLabel lblType3 = new JLabel("byte (TYPE_BYTE_GRAY)");
		lblType3.setHorizontalAlignment(SwingConstants.CENTER);
		panel_3.setColumnHeaderView(lblType3);
		panel_3.setViewportView(lblGrey);
		
		btnLoadImage.addActionListener(new ActionListener()
			{
				public void actionPerformed(ActionEvent arg0)
				{
					int ret = fileChooser.showOpenDialog(ImageBsp.this);
					if (ret == JFileChooser.APPROVE_OPTION)
					{
						try
						{
							imgOriginal = ImageIO.read(fileChooser.getSelectedFile());
							if (imgOriginal != null)
							{
								imgByteGrey = toByteGreyImg(imgOriginal);
								imgProcessed = toByteGreyImg(imgByteGrey);
								
								lblType.setText("Original (" + getTypeString(imgOriginal) + ")");
								lblProcType.setText("Processed ("+getTypeString(imgProcessed)+")");
								
								lblOriginal.setIcon(new ImageIcon(imgOriginal));
								lblGrey.setIcon(new ImageIcon(imgByteGrey));
								lblProcessed.setIcon(new ImageIcon(imgProcessed));
								updateImage();
							}
							
						} catch (IOException e)
						{
							// TODO Auto-generated catch block
							e.printStackTrace();
						}
					}
				}
			});
		
		fileChooser = new JFileChooser();
	}
	
	public BufferedImage toByteGreyImg(Image img)
	{
		BufferedImage greyImg = new BufferedImage(img.getWidth(null), img.getHeight(null), BufferedImage.TYPE_BYTE_GRAY);
		Graphics g = greyImg.getGraphics();
		g.drawImage(img, 0, 0, null);
		g.dispose();
		return greyImg;
	}
	
	private void processArray(int[] imgData, int upper, int lower)
	{
		int ug = Math.min(upper,lower);
		int og = Math.max(upper,lower);
		int ratio=og-ug;
		
		for (int i = 0; i < imgData.length; i++)
		{
			int pixel=imgData[i];
			int alpha = pixel & 0xFF000000;
			pixel &=0xFFFFFF;
			if (pixel >= ug && pixel <= og)
			{
				pixel = (int) (((pixel - ug) / (double)ratio) * 255.0);
				
			} else if (pixel < ug)
			{
				pixel = 0;// schwarz
			} else
			{
				pixel = 0xFF;// weiß
			}
			imgData[i]= alpha | pixel << 16 | pixel << 8 | pixel;
			
		}
		
	}
	
	private String getTypeString(BufferedImage img)
	{
		int type = img.getType();
		for (Field f : BufferedImage.class.getDeclaredFields())
		{
			if (f.getName().startsWith("TYPE"))
			{
				try
				{
					if (f.getInt(img) == type)
					{
						return f.getName();
					}
				} catch (IllegalArgumentException e)
				{
					e.printStackTrace();
				} catch (IllegalAccessException e)
				{
					e.printStackTrace();
				}
			}
		}
		return "Not found";
	}
	
	private synchronized void updateImage()
	{
		long start=System.nanoTime();
		if (imgByteGrey != null)
		{
			int w = imgByteGrey.getWidth();
			int h = imgByteGrey.getHeight();
			int[] arr = new int[w * h];
			imgByteGrey.getData().getPixels(0, 0, w, h, arr);
			processArray(arr, lowerBound, upperBound);
			imgProcessed.setRGB(0, 0, w, h, arr, 0, w);
			lblProcessed.repaint();
		}
		long end=System.nanoTime();
		System.out.println("Time to calculate: "+(end-start)+" ns");
	}
}
 
Zuletzt bearbeitet:

Marco13

Top Contributor
Ich kapier' immernoch nicht, warum du mit einem eigenen ColorModel etc. rummachst. Aber irgendwie habe ich das Gefühl, dass diese Frage für immer unbeantwortet bleibt, und dieser Thread hier irgendwie ziemlich ziel- und planlos vor sich hin dümpelt...
 
G

Guest2

Gast
Moin,

das Bild oben wiest sich hier als RGB mit 8 Bit pro Kanal aus. 16 Bit Graustufen wäre was anderes, ist da vielleicht was schief gelaufen? (PNG unterstützt sowohl 16 Bit pro Kanal als auch Graustufen)

(Das eigentliche Problem sehe ich allerdings auch nicht... bzw., ich würde im Zweifelsfall wohl einen Shader schreiben ;))

Viele Grüße,
Fancy
 

bERt0r

Top Contributor
Das Bild das er gepostet hat ist ein 24 bit png aber ich glaube nicht dass die 24 Bit für die Graustufen draufgehen. Gibt es sowas wie 16Bit Graustufen überhaupt (als geläufiges Format) und braucht man 32000 Abstufungen von Grau? Wenn ja, dann muss man so ein 16Bit Graustufen bild aus einem 64 bit ARGB berechnen, sonst geht sich das gar nicht aus.
 

Planich

Aktives Mitglied
Ich kapier' immernoch nicht, warum du mit einem eigenen ColorModel etc. rummachst. Aber irgendwie habe ich das Gefühl, dass diese Frage für immer unbeantwortet bleibt, und dieser Thread hier irgendwie ziemlich ziel- und planlos vor sich hin dümpelt...

Warum denn eigenes ColorModel?

Mein Problem ist, dass hier jeder etwas anderes sagt. Der eine sagt ich soll das so machen und der andere so.

Ich will einfach nur meine verschiedenen Formate (bei bertors Code geht z.B. wieder kein tiff) einlesen können und dann meine Grenzen setzen. Warum ich da so ein für euch komisches ColorModel habe? Keine Ahnung. Weil das beim Erstellen so rauskommt. Ich hab doch da keinen Einfluss drauf. Und wenn dann weiß ich nicht wie. Ich frickel hier seit Wochen rum. Schnappe hier Code und da Code auf und probiere mich durch. Das ich z.B. schon mehrfach angesprochen habe, dass ich das mit dem ColorModel nicht verstehe scheint z.B. an euch vorbei gegangen zu sein. Es gibt da einfach gewisse Dinge die ich noch nicht kapiert hab. Und das ist eine Sache davon. Ich bin nicht so erfahren, ich programmiere noch nicht so lange. Und für mich ist das scheinbar schon ein Tick zu hoch.

Schön wäre es gewesen wenn mir mal jemand die kritischen Stellen erklärt als nur den Code hinzuwerfen. Insgesamt gibt es nicht so viele kritische Stellen


Was mich zum Beispiel brennend interessiert ist, die Umwandlung von meinem eingelesen RenderedImage in ein geeigneten BufferedImage Typ. Wobei ihr drei, soweit ich das mitbekommen habe, völlig andere Herangehensweisen und auch am Ende verschiedene BufferedImages habt.
 

Planich

Aktives Mitglied
Das Bild das er gepostet hat ist ein 24 bit png aber ich glaube nicht dass die 24 Bit für die Graustufen draufgehen. Gibt es sowas wie 16Bit Graustufen überhaupt (als geläufiges Format) und braucht man 32000 Abstufungen von Grau? Wenn ja, dann muss man so ein 16Bit Graustufen bild aus einem 64 bit ARGB berechnen, sonst geht sich das gar nicht aus.
Wie man dem Grafiknamen entnehmen kann war das ursprünglich mal eine Tiff
ist mir gar nicht aufgefallen. Das hat der Hoster wohl einfach umgewandelt.
Wenn mir jemand nen guten Host nennt, lad ich nochmal was hoch
 

Marco13

Top Contributor
Das wäre jetzt auch meine Nachfrage gewesen: Mal so ein Bild hochladen (notfalls hier als Anhang), und genau sagen, was die eigentliche Frage bzw. das eigentliche Ziel ist.
 

bERt0r

Top Contributor
In einem vernünftigen Programm mit klarer Strukturierung sollte es egal sein wie du das Image einliest. Wenn du mit meinem Programm tiffs einlesen willst musst du eben deinen Tiff Code inklusive umwandlung in ein Image vorschalten. Du kannst z.B eine eigene Klasse ImageLoader machen die dir das ganze Einlesen kapselt und an das Restprogramm ein BufferedImage zurückgibt. Den code dafür hast du bereits gepostet, dem fehlt nur die Struktur. Sobald du dein Image mal im Speicher hast ist es auch vollkommen egal in welchem Format es vorher gespeichert wurde.
Und die Umwandlung eines RenderedImage in ein BufferedImage hast du doch schon gepostet. Das sollte doch einwandfrei funktionieren. public BufferedImage convertRenderedImage(RenderedImage img)
 
Zuletzt bearbeitet:

Marco13

Top Contributor
Naja, bei diesen "besonderen" TIFFs (mit 16bit Graustufen, wie sie z.B. im medizinischen Bereich, für Röntgenaufnahmen und son Zeux verwendet werden) muss man schon ein bißchen aufpassen: Wenn man das "einfach" in "irgendein" BufferedImage reinzeichnet, geht ja Information verloren. Das besondere ist wohl, dass die gesamte im ursprünglichen Bild enthaltene Information erstmal beibehalten werden soll, und nur ein (auf ganz bestimmte, selbst zu definierende Weise) Information "weggeworfen" und das Bild in ein 8bit-Graustufen-Bild umgewandelt werden soll (und das schnell, und noch in einem Format, das sich schnell zeichnen läßt)

Aber... woran es jetzt noch hakt oder was die eigentliche Frage ist ... hmja... mal sehen was da noch kommt.
 

bERt0r

Top Contributor
Wenn er ein 16 Bit graustufen bild braucht, bleibt ihm nichts anders über als das ganze über JAI (Java Advanced Imaging) zu lösen.
Hier ein Tutorial: http://seer.ufrgs.br/rita/article/download/rita_v11_n1_p93-124/3555
Hier der DL-Link: Java Archive Downloads -Java Client Technologies
Die Frage ist, kann der Computer das überhaupt anzeigen? Bei 16-Bit Graustufen hätte man ein RGB Farbtiefe von 64 Bit. Das unterstützt meine Grafikkarte zumindest nicht - drum steht da Farbtiefe 32 Bit (höchste)
 
Zuletzt bearbeitet:
S

Spacerat

Gast
Hmm... beim hin und her überlegen, was es mit bERt0r's Aussage über die 64-Bit RGB und der Tatsache, dass das die wenigsten GraKas hergeben, fällts mir auf... (@bERt0r: natürlich... Grau benötigt in einer Mischanzeige mit anderen Farben logischerweise auch 3 Kanäle @16Bit. ;)) Willst du zufällig das 16-Bit Histogramm so abändern, dass die meisten Kontraste in die 8 Bit passen?
Nochmal zum besseren Verständnis, warum [c]<Raster>.getDataBuffer()[/c] ein BufferedImage um ein Vielfaches verlangsamt, etwas "malerisches" im Anhang. Sicher - hinter dem "trusted Code" verbergen sich einige Verivizierungsmethoden, die schlimmste von allen dürfte die Umrechnungsmethode von Alpha auf AlphaPremultiplied sein. Aber nichts desto trotz geht zumindest das Lesen (z.B. copyImage beim Anzeigen in Graphics) aus einem verifizierten Buffer schneller, als das aus einem unverifizierten, weil den Daten, die im Puffer liegen vertraut werden kann, da diese bereits beim Schreiben verifiziert wurden. Nur einem vertrautem Puffer kann das Privilleg zu Teil werden, in der Hardware zu landen, wo der Lesezugriff native noch mal um einiges schneller geht. In genau dem Augenblick, wo der Datenpuffer bzw. seine Arryas in Java verfügbar sind, hat man verloren, was die Performance angeht. Genau das führt einem zur Verwendung der Klassen auf der "trusted Code"-Seite, z.B. ColorModel in diesem Fall.
Was mich grad' stuzig macht, ist die Aussage, dass mein KSKB auch grotten langsam sein soll, aber ich nehm' das mal so hin und werd's mir mal ansehen. Zufällig habe ich schon eine Idee, woran das liegt - nämlich am WritableRaster des RenderedImage, welches keine Instanz von SunWritableRaster ist. Von der Logik her müsste man nämlich eben dieses erweitern, um überhaupt in den Genuss der Java2D-Hardwarebeschleunigung zu kommen. JAI könnte man dann glaub' ich auch vergessen...
[ot]...und um bERt0r mal ein wenig zu ärgern... :lol: Nö... es gäbe noch eine andere Möglichkeit, diese muss aber erst spruchreif werden. Hat was mit der Codezeile zu tun, die der TO einige Posts zuvor in meinem KSKB ändern musste oder aber auch mit deiner ImageLoader-Idee. "ImageLoader" :D ... Ich schreibe schon seit Jahren an so 'nem standartisierten DateiLoader... für Sound-, Text-, Bild- und Animationsdateien kein Problem. Eigentlich hakt es dort grad' nur an einem 3D-Object-Interface und viele andere Formate, über die ich mir noch keinen Kopf machen konnte.[/ot]
 

Anhänge

  • BufferedImage.png
    BufferedImage.png
    33,5 KB · Aufrufe: 31
G

Guest2

Gast
@bERt0r:

Wenn Du eine dedizierte Consumer-Grafikkarte hast, kann Deine Grafikkarte intern zwar mit 16 Bit pro Kanal rechnen diese aber nicht an den Monitor ausgeben. Wenn mehrere Texturen / Texel pro Pixel verrechnet werden müssen, kann das sinnvoll sein, da es sonnst durch numerische Instabilitäten zu sichtbaren Artefakten kommen kann.

Die Worstation-Grafikkarten können (den passenden Monitor vorausgesetzt) zumindest 10 Bit pro Kanal ausgeben.

Unüblich sind solche Formate auch nicht. Tiff und PNG können z.B. problemlos mit 16 Bit pro Kanal umgehen. RAW bei Digitalkameras sind auch oft 12 oder 14 Bit pro "Kanal". Viele Grafikprogramme können das ebenfalls handeln (oder sogar noch mehr).



Wenn ich Planich aber richtig verstehe, möchte er einfach aus den 16 Bit einen Bereich spreizen und den dann darstellen. Beim Bild oben sieht mir das z.B. so aus, als ob links neben dem Batteriekasten noch eine Platine mit zwei quadratischen Bauteilen zu erkennen ist. Bei einem 16 Bit Graustufenbild kann man das vermutlich problemlos erkennen und entscheiden ob es ein Ladegerät oder eine Bombe ist. ;)

Viele Grüße,
Fancy
 
S

Spacerat

Gast
Das Ganze nochmal zum verifizieren...
Java:
import com.sun.media.jai.codec.ImageCodec;
import java.awt.BorderLayout;
import java.awt.Container;
import java.awt.Dimension;
import java.awt.Graphics;
import java.awt.Point;
import java.awt.image.BufferedImage;
import java.awt.image.ColorModel;
import java.awt.image.DataBuffer;
import java.awt.image.Raster;
import java.awt.image.RenderedImage;
import java.awt.image.WritableRaster;
import java.io.File;
import java.io.FileNotFoundException;

import javax.swing.JComponent;
import javax.swing.JFrame;
import javax.swing.JSlider;
import javax.swing.event.ChangeEvent;
import javax.swing.event.ChangeListener;

import datatypes.DataType;
import datatypes.imaging.Image;


public class OwnPanel
extends JComponent
{
	private static final long serialVersionUID = 1L;

	private final OwnImage img;

	private OwnPanel(OwnImage img)
	{
		super();
		if(img == null) {
			throw new NullPointerException("img may not be null");
		}
		this.img = img;
		Dimension size = new Dimension(img.getWidth(), img.getHeight());
		setMinimumSize(new Dimension(1, 1));
		setPreferredSize(size);
		setSize(size);
	}

	@Override
	protected void paintComponent(Graphics g)
	{
		super.paintComponent(g);
		g.drawImage(img, 0, 0, getWidth(), getHeight(), null);
	}

	public static void main(String[] args)
	{
		if(args == null || args.length == 0) {
			args = new String[] {"tiff16bit.tiff"};
		}
		JFrame f = new JFrame("OwnImageTest");
		f.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
		Container cp = f.getContentPane();
		final JSlider upper = new JSlider(JSlider.VERTICAL);
		final JSlider lower = new JSlider(JSlider.VERTICAL);
		final OwnPanel op = new OwnPanel(new OwnImage(new File(args[0]).getAbsoluteFile()));
		cp.setLayout(new BorderLayout());
		cp.add(op, BorderLayout.CENTER);
		cp.add(upper, BorderLayout.EAST);
		cp.add(lower, BorderLayout.WEST);
		upper.setMaximum(0xFFFF);
		upper.setMinimum(0);
		upper.setValue(0xFFFF);
		upper.addChangeListener(new ChangeListener()
		{
			@Override
			public void stateChanged(ChangeEvent e)
			{
				int v = upper.getValue();
				op.img.setOgDraw(v);
				lower.setMaximum(v);
				op.repaint();
			}
		});
		lower.setMaximum(0xFFFF);
		lower.setMinimum(0);
		lower.setValue(0);
		lower.addChangeListener(new ChangeListener()
		{
			@Override
			public void stateChanged(ChangeEvent e)
			{
				int v = lower.getValue();
				op.img.setUgDraw(v);
				upper.setMinimum(v);
				op.repaint();
			}
		});
		f.pack();
		f.setVisible(true);
	}
}

class OwnImage
extends BufferedImage
{
	private final BufferedImage original;

	public OwnImage(File f)
	{
		this(new Loader(f));
	}

	private OwnImage(Loader l)
	{
		super(l.cm, l.data, false, null);
		original = l.originalImage;
		getGraphics().drawImage(l.originalImage, 0, 0, null);
	}

	public synchronized void setUgDraw(int ug)
	{
		((OwnColorModel) getColorModel()).setUg(ug);
		getGraphics().drawImage(original, 0, 0, null);
	}

	public synchronized void setOgDraw(int og)
	{
		((OwnColorModel) getColorModel()).setOg(og);
		getGraphics().drawImage(original, 0, 0, null);
	}

	public synchronized void setUgOgDraw(int ug, int og)
	{
		((OwnColorModel) getColorModel()).setBorders(ug, og);
		getGraphics().drawImage(original, 0, 0, null);
	}

	private static class Loader
	{
		private ColorModel cm;
		private WritableRaster data;
		private BufferedImage originalImage;

		private Loader(File f)
		{
			try {
	            //Rendered Image erstellen
	            RenderedImage ri = ImageCodec.createImageDecoder("TIFF", f, null).decodeAsRenderedImage();
				ColorModel cm = ri.getColorModel();
				WritableRaster wr = ri.getData().createCompatibleWritableRaster();
				BufferedImage tmp = new BufferedImage(cm, ri.copyData(wr), cm.isAlphaPremultiplied(), null);
				originalImage = new BufferedImage(ri.getWidth(), ri.getHeight(), BufferedImage.TYPE_USHORT_GRAY);
				originalImage.getGraphics().drawImage(tmp, 0, 0, null);
			} catch(FileNotFoundException e) {
				e.printStackTrace();
				return;
			}
			int min = Integer.MAX_VALUE;
			int max = Integer.MIN_VALUE;
			Raster r = originalImage.getData();
			Object buffer = new short[1];
			int w = r.getWidth();
			int h = r.getHeight();
			int c = max;
			for(int x = 0; x < w; x++) {
				for(int y = 0; y < h; y++) {
					c = ((short[]) r.getDataElements(x, y, buffer))[0] & 0xFFFF;
					if(min > c) {
						min = c;
					}
					if(max < c) {
						max = c;
					}
				}
			}
			data = Raster.createInterleavedRaster(DataBuffer.TYPE_BYTE, w, h, w, 1, new int[] {1}, new Point());
			cm = new OwnColorModel(min, max);
		}
	}

	private static class OwnColorModel
	extends ColorModel
	{
		private int og, ug;
		private double ratio;

		public OwnColorModel(int ug, int og)
		{
			super(8);
			setBorders(ug, og);
		}

		@Override
		public boolean isCompatibleRaster(Raster raster)
		{
			return true;
		}

		@Override
		public int getRed(int pixel)
		{
			return pixel2RGB(pixel);
		}

		@Override
		public int getGreen(int pixel)
		{
			return pixel2RGB(pixel);
		}

		@Override
		public int getBlue(int pixel)
		{
			return pixel2RGB(pixel);
		}

		@Override
		public int getAlpha(int pixel)
		{
			return 0xFF;
		}

		@Override
		public int getRGB(Object inData)
		{
			if(inData == null) {
				throw new NullPointerException("inData may not be null");
			}
			if(inData instanceof byte[]) {
				return pixel2RGB(((byte[]) inData)[0]);
			} else if(inData instanceof short[]) {
				return pixel2RGB(((short[]) inData)[0]);
			} else if(inData instanceof int[]) {
				return pixel2RGB(((int[]) inData)[0]);
			} else {
				return 0;
			}
		}

		@Override
		public int getRGB(int pixel)
		{
			return pixel2RGB(pixel);
		}

		@Override
		public Object getDataElements(int rgb, Object pixel)
		{
			rgb = rgb2Pixel(rgb);
			if(pixel == null) {
				pixel = new byte[1];
			}
			if(pixel instanceof byte[]) {
				((byte[]) pixel)[0] = (byte) (rgb & 0xFF);
			} else if(pixel instanceof short[]) {
				((short[]) pixel)[0] = (short) (rgb & 0xFFFF);
			} else if(pixel instanceof int[]) {
				((int[]) pixel)[0] = rgb;
			}
			return pixel;
		}

		private void setUg(int ug)
		{
			setBorders(ug, og);
		}

		private void setOg(int og)
		{
			setBorders(ug, og);
		}

		private void setBorders(int ug, int og)
		{
			og &= 0xFFFF;
			ug &= 0xFFFF;
			this.ug = Math.min(ug, og);
			this.og = Math.max(ug, og);
			ratio = this.og - this.ug;
		}

		private final int rgb2Pixel(int rgb)
		{
			if((rgb & 0xFFFF) != Math.abs(rgb)) {
				// für den Fall, dass beliebige (A)RGB-Werte (> 16 Bit) übergeben werden
				rgb &= 0xFFFFFF;
				int r = (rgb >> 16) & 0xFF;
				int g = (rgb >>  8) & 0xFF;
				int b = (rgb >>  0) & 0xFF;
				if(r != g || r != b || g != b) {
					// für den Fall, dass diese Werte nicht mal grayscaled sind
					r = (int) (r * 0.3 + g * 0.59 + b * 0.11);
				}
				rgb = r << 8 | r;
			}
			if(rgb >= ug && rgb <= og) {
				rgb = (int) (((rgb - ug) / ratio) * 255.0) & 0xFF;
			}
			else if(rgb < ug) {
				rgb = 0;//schwarz
			}
			else {
				rgb = 0xFF;//weiß  
			}
			return rgb;
		}

		private final int pixel2RGB(int pixel)
		{
			pixel &= 0xFF;
			pixel = (int) (((pixel / 255.0) * ratio) + ug);
			if(pixel < ug) {
				pixel = ug;
			}
			else if(pixel > og) {
				pixel = og;
			}
			pixel >>= 8 & 0xFF;
			return pixel | pixel << 8 | pixel << 16 | 0xFF000000;
		}
	}
}
Wenn ich aus dem PNG wie im Code ein TYPE_USHORT_GRAY mache, geht das alles ziemlich flott wie auch bei anderen Dateitypen. Klar... RenderedImage kann nicht direkt in ein Graphics-Objekt gezeichnet werden, schliesslich ist's ein Interface und kann deshalb kaum Image erweitern :oops:. Zusätzlich kann das darin enthaltene Raster sonstwas sein und muss auch nicht zwangsläufig SunWritableRaster erweitern. Aber so ist das nun mal mit die Logik... für die Java2D-Hardwarebeschleunigung muss es ein halt ein solches sein ;).
Wenn an dieser Anwendung nun irgend etwas noch nicht so hinhaut wie gedacht, könnte das an den Slidern liegen. "originalImage" ist nun aber auf jeden Fall ein hardwarebeschleunigtes BufferedImage vom Typ TYPE_USHORT_GRAY.
 

Planich

Aktives Mitglied
Da ich heut ein bisschen in die Praxis muss, komme ich nicht zum Coden. Trotzdem nochmal kurz ein Statement:

Ja genau, erst durch das Spreizen eines bestimmten Bereichs werden gewisse Details erkannt. Z.B. ob es ein Draht von einer Bombe oder eben doch gar nichts ist.

Und ja für TIFF braucht man JAI, was ich nicht ohne Grund verwende. Doch so trivial wie ihr den Loader dafür bezeichnet, ist es für mich nicht. Zumindest dann nicht, wenn es darum geht, sich den Buffer nicht zu holen, was ja die ganze Zeit von Spacerat vorgeschlagen und angeraten wird. (Wodurch ja das eigentliche Problem entsteht)

Eine temporäre Lösung des ganzen hatte ich ja bereits(ohne darauf zu achten sich den Buffer nicht zu holen), mit ein klein wenig Performanceproblemen, die aber eher als gering bezeichnet werden dürfen, vergleicht man das mit dem Anfangsstadium (vorher 1600ms jetzt 250ms - so ganz grob)

Um nochmal auf eure Monitor Grafikkarte -Diskussion zurück zu kommen. 16bit Grau kann in der Regel kein Monitor darstellen. Deshalb bricht man das ja auch runter auf die angesprochenen 8bit. Je nach Anwendung werden dann entweder die vollen 16bit auf die 8bit linear verteilt oder man nimmt max und min oder man gibt einen bestimmten Bereich vor. Es gibt auch spezielle Monitore, die mehr als diese 8 bit darstellen können. Da diese jedoch eher teuer sind und auch für meine Anwendung nicht gedacht, finden diese in der Applikation keine Beachtung.
 

Planich

Aktives Mitglied
Hier nochmal das Bild als richtiges 16bit TIFF


müsste jetzt klappen denk ich

@Spacerat

Wenn ich deinen Code benutze, dauert es schon etwa 2 sek bevor das GUI die neue Slider Position darstellt, also wo ich ihn mit der Maus hingezogen habe. Das Zeichnen mit den Werten der aktuellen Position geht dann aber sehr flott.
Was auch stört ist die Qualität. Ich weiß noch nicht warum, aber es ist total blass und kontrastarm. Kein richtiges weiß, kein richtiges schwarz
 
Zuletzt bearbeitet:

Marco13

Top Contributor
Bei mir ist das immernoch ein PNG, aber eins mit 48 bit. D.h. es hat 3 Kanäle mit je 16 bit.

Wenn man dieses Programm auf das hochgeladene 48bit-PNG losläßt, macht er eine normalisierung, d.h. er erhöht den Kontrast des Bildes aufs maximum, und zeigt das input- und output-Bild nebeneinander an. Die Grenzen für diese "Normalisierung" werden automatisch berechnet, und man kann sie nachträglich mit den Slidern ändern. Die Konvertierung dauert ca. 100ms.

Das ganze braucht kein JAI, kein ColorModel, kein Raster-Gefrickel. Einfach mit ImageIO laden und los.

Java:
import java.awt.BorderLayout;
import java.awt.Graphics;
import java.awt.Graphics2D;
import java.awt.GridLayout;
import java.awt.image.BufferedImage;
import java.awt.image.DataBuffer;
import java.awt.image.DataBufferUShort;
import java.awt.image.RenderedImage;
import java.io.File;
import java.io.IOException;

import javax.imageio.ImageIO;
import javax.swing.JFrame;
import javax.swing.JLabel;
import javax.swing.JPanel;
import javax.swing.JSlider;
import javax.swing.SwingUtilities;
import javax.swing.event.ChangeEvent;
import javax.swing.event.ChangeListener;

import com.sun.media.jai.codec.ImageCodec;
 
 
class ImagePanel extends JPanel
{
    private BufferedImage image;
    
    public ImagePanel()
    {
    }
    
    public void setImage(BufferedImage image)
    {
        this.image = image;
        repaint();
    }
    
    @Override
    protected void paintComponent(Graphics g)
    {
        super.paintComponent(g);
        if (image != null)
        {
            long before = System.nanoTime();
            g.drawImage(image, 0, 0, getWidth(), getHeight(), null);
            long after = System.nanoTime();
            //System.out.println("Painting "+(after-before)/1e6+" ms");
        }
    }
    
}
 
public class ShortToByteImageTest extends JPanel
{
    public static void main(String[] args)
    {
        SwingUtilities.invokeLater(new Runnable()
        {
            @Override
            public void run()
            {
                createAndShowGUI();
            }
        });
    }
    
    public static String nameOf48bitPNG = "image.png";
    
    private ImagePanel inputImagePanel;
    private ImagePanel outputImagePanel;
    private BufferedImage shortImage;
    private short shortImageData[];
    
    public ShortToByteImageTest()
    {
        super(new BorderLayout());
        
        shortImage = obtainImageWithShortBufferFromSomewhere();
        shortImageData = getShortArray(shortImage);

        JPanel p = new JPanel(new GridLayout(1,2));
        inputImagePanel = new ImagePanel();
        p.add(inputImagePanel);
        inputImagePanel.setImage(shortImage);
        
        outputImagePanel = new ImagePanel();
        p.add(outputImagePanel);
        add(p, BorderLayout.CENTER);
        
        JPanel controlPanel = new JPanel(new GridLayout(2,0));
 
        JPanel pMin = new JPanel(new BorderLayout());
        final JSlider minSlider = new JSlider(
            -Short.MAX_VALUE, Short.MAX_VALUE, -Short.MAX_VALUE);
        pMin.add(new JLabel("min"), BorderLayout.WEST);
        pMin.add(minSlider, BorderLayout.CENTER);
        controlPanel.add(pMin);
        
        JPanel pMax = new JPanel(new BorderLayout());
        final JSlider maxSlider = new JSlider(
            -Short.MAX_VALUE, Short.MAX_VALUE, Short.MAX_VALUE);
        pMax.add(new JLabel("max"), BorderLayout.WEST);
        pMax.add(maxSlider, BorderLayout.CENTER);
        controlPanel.add(pMax);
        
        add(controlPanel, BorderLayout.SOUTH);
        
        ChangeListener changeListener = new ChangeListener()
        {
            @Override
            public void stateChanged(ChangeEvent e)
            {
                int min = minSlider.getValue();
                int max = maxSlider.getValue();
                updateImage(shortImage.getWidth(), shortImage.getHeight(), min, max);
            }
        };
        minSlider.addChangeListener(changeListener);
        maxSlider.addChangeListener(changeListener);
        
        int min3 = min3(shortImageData);
        int max3 = max3(shortImageData);
        System.out.println("min3 of image is "+min3);
        System.out.println("max3 of image is "+max3);
        minSlider.setValue(min3);
        maxSlider.setValue(max3);
    }
    
    private static int min3(short input[])
    {
    	int min = Integer.MAX_VALUE;
        for (int i=0; i<input.length/3; i++)
        {
            min = Math.min(min, input[i*3+0]);
        }
        return min;
    }
    private static int max3(short input[])
    {
    	int max = -Integer.MAX_VALUE;
        for (int i=0; i<input.length/3; i++)
        {
            max = Math.max(max, input[i*3+0]);
        }
        return max;
    }
    
    
    private void updateImage(int w, int h, int min, int max)
    {
        long before = System.nanoTime();
        byte bData[] = convertShortToByteArray(shortImageData, min, max);
        BufferedImage iImage = createImageFromByteArray(w, h, bData);
        long after = System.nanoTime();
        System.out.println("Conversion with min="+min+" max="+max+" took "+(after-before)/1e6+" ms");
        outputImagePanel.setImage(iImage);
    }
 
    
    private static BufferedImage obtainImageWithShortBufferFromSomewhere()
    {
    	try {
			return ImageIO.read(new File(nameOf48bitPNG));
		} catch (IOException e) {
			e.printStackTrace();
			return null;
		}
    }
    
    private static short[] getShortArray(RenderedImage image)
    {
        DataBuffer dataBuffer = image.getData().getDataBuffer();
        DataBufferUShort databufferUShort = (DataBufferUShort)dataBuffer;
        return databufferUShort.getData();
    }

    
    private static byte[] convertShortToByteArray(short input[], int min, int max)
    {
        byte output[] = new byte[input.length/3];
        float normalization = 1.0f / (max - min);
        for (int i=0; i<input.length/3; i++)
        {
            float f = (input[i*3+0]-min)*normalization;
            output[i] = (byte)(f * 256);
        }
        return output;
    }
    
    private static BufferedImage createImageFromByteArray(int w, int h, byte input[]) 
    {   
        int output[] = new int[input.length];
        for (int i = 0; i < input.length; i++) 
        {
            byte v = input[i];
            int a = 0xFF000000;
            int r = (v & 0xFF) << 16; 
            int g = (v & 0xFF) <<  8; 
            int b = (v & 0xFF) <<  0; 
            output[i] = a | r | g | b;
        }
        BufferedImage result = new BufferedImage(w, h, BufferedImage.TYPE_INT_ARGB);
        result.setRGB(0,0, w, h, output, 0, w);
        return result;
    }
    
    private static void createAndShowGUI()
    {
        JFrame f = new JFrame();
        f.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
        f.getContentPane().add(new ShortToByteImageTest());
        f.setSize(1200,800);
        f.setVisible(true);
    }
    
}


Wenn's das jetzt noch nicht war, geb' ichs vielleicht einfach auf...
 

Planich

Aktives Mitglied
Danke Marco und euch allen. Ich gebs jetzt mittlerweile auch auf das ganze noch weiter zu verbessern.

Ich verwerfe die Idee das so zu machen wie Spacerat, weil ich das einfach nicht hinbekomme ohne das ich mir den Buffer hole

Ohne JAI geht nicht, weil ich dann keine Tiffs einlesen kann. Es ist jetzt nicht ultra performant was ich an Code habe,aber es läuft ganz gut. Ich denke darauf kann ich aufbauen.

Das dieser Mist jetzt wieder implizit beim Hochladen nach png gewandelt wurde ist einfach nur noch dämlich. Wenn jemand Interesse hat, schick ichs ihm per Mail, dann einfach nur ne PM an mich.

Danke euch allen
 
S

Spacerat

Gast
und für mich noch das Grosse als tiff-zip... aber versuch's mal ohne diesen komischen Hoster, einfach hier per Datei-Anhang (geht, wenn man beim Beitrag auf "antworten" klickt, anstatt "direkt antworten" zu verwenden). Es widerstrebt mir, überall ständig meine E-Mail-Adresse zu hinterlassen. Aber eines ist sicher... ich bekomm' das hin! ;)
 

Planich

Aktives Mitglied
was genau meinst du mit auf antworten klicken?
bei mir steht, das mein File zu groß ist. Hier gibt es eine Beschränkung oder was meinst du wie man die umgeht?
 

Planich

Aktives Mitglied
Hier mal meine aktuelle Version des Erstellens eines "guten" BufferedImages

Java:
public void shortToImage(short[] data,int ug, int og)
{          byte output=0;
            int outputArr[] = new int[byteArr.length];
    
            //Berechnen der Hälfte - Schnittpunkt Hälfte ist weiß und schwarz
            short half=(short)(((og-ug)>>1));
                       
            //float ratio = (og-ug)/256;
            short ratio =(short)( (og-ug));
            
            
            //-32768 ist grau|| -1 weiß|| 0 schwarz || 32767 grau 
            for (int i=0;i<data.length;i++)
            {
                if(data[i]>=ug&&data[i]<=og)
                {
                    //Schwarzstufen
                    if(data[i]<og-(half+1))                    
                    {                            
                        output=(byte)(((data[i]-ug)<<8)/ratio);                     
                    }
                    //Weißstufen     
                    else
                    {                    
                        output=(byte)(((data[i]-ug)<<8)/ratio-256);
                        //Falls Rundungsfehler (-0.3 wäre 0 und damit schwarz), manuelles setzen auf -1 ->entspricht weiß
                        if(output==0)
                        {
                            output=-1;
                        }
                    }
                }
                if(data[i]<ug)
                    output=0;//schwarz
                if(data[i]>og)
                    output=-1;//weiß  
            
            //byte v = output;
            int a = 0xFF000000;
            int r = (output & 0xFF) << 16; 
            int g = (output & 0xFF) <<  8; 
            int b = (output & 0xFF) <<  0; 
            outputArr[i] = a | r | g | b;                       
            }
            
            Image im= createImage(new MemoryImageSource(width, height,outputArr , 0, width));      
            changedImage.getGraphics().drawImage(im, 0,0,width,height,null);//KOMISCHE STELLE  
            changedImage.getGraphics().dispose();
            
}

ich habe zwei Methoden zusammen gepackt. Ich erstelle nicht erst ein byteArr durch ein ShortArr und zeichne das dann ins BI sondern verknüpfe beides und spare dadurch eine for Schleife. Jedoch bringt das keinen Performancegewinn (was das angeht habe ich auch noch nicht wirklich Erfahrung.

Was mir auffällt ist die vorletzte Zeile. Diese springt von der Dauer bei jedem mal hin und her. Als Beispiel: Beim ersten Aufruf 70ms beim zweiten 130ms beim dritten wieder 72ms und beim vierten 134ms ...

Kann das jemand nachvollziehen?


[EDIT]@Spacerat nochmal zu deinem Code

Java:
public synchronized void setUgDraw(int ug)
    {
        long t1=System.currentTimeMillis();
        ((OwnColorModel) getColorModel()).setUg(ug);
        getGraphics().drawImage(original, 0, 0, null);
        System.out.print("Draw   "); System.out.println(System.currentTimeMillis()-t1);
    }

An dieser Stelle habe ich schon eine Dauer von ca. 500-600 ms
Soviel braucht der Code von mir nichtmal insgesamt vom Slider bis zum fertigen Bild
Also irgendwas haut da nicht so hin, wie von dir gewünscht[/EDIT]
 
Zuletzt bearbeitet:

bERt0r

Top Contributor
Ich wiederhols nochmal komprimiert:

1. Tiff laden
2. Tiff in ein temporäres BufferedImage umwandeln (TYPE_CUSTOM).
3. TemporäresBufferedImage in ein TYPE_USHORT_GRAY BufferedImage umwandeln (das hat deine 16 bit so wie du es dir vorstellst)
4. Sich das shortArray aus dem Databuffer des TYPE_USHORT_GRAY schnappen. Wir wollen dieses Image nicht anzeigen, daher ist uns managed/unmanaged hier egal.
5. Das array normalisieren und in ein int argb Format umwandeln.
6. Ein TYPE_BYTE_GRAY image erstellen und mit setPixels die normalisierten pixeldaten zuweisen. Extra ein neues Image bei jedem Berechnen zu erstellen ist nicht notwendig.

Wichtig ist ausserdem, dass wenn du z.B einen Slider verwendest, du nicht bei jedem Change event neu berechnest bzw. wenn du feststellst es ist bereits eine Berechnung im Gange, diese abbrichst wenn ein neues Change Event hereinkommt.

Bei deiner Berechnungsfunktion könne man sicher auch noch etwas optimieren: Wenn du deinen Pixelwert gleich in eine Form bringst, in der man ihn anständig vergleichen kannst, ersparst du dir ein paar Rechenoperationen.
Java:
private int[] processPixels(short[] imgData, int upper, int lower)
	{
		int ug = Math.min(upper,lower);
		int og = Math.max(upper,lower);
		int spektrum=og-ug;
		int[] pixels=new int[imgData.length];
		for (int i = 0; i < imgData.length; i++)
		{
			int pixel=imgData[i];
			
			//-1 ist Weiß bei USHORT_GRAY
			if(pixel<0)
			{
				pixel=Short.MAX_VALUE+(pixel-Short.MIN_VALUE);  //Bildet die short quasi auf 0-65533 ab, wobei 0 Schwarz und 65533=Weiß
			}
			if (pixel >= ug && pixel <= og)
			{
				pixel = (int) (((pixel - ug) / (double)spektrum) * 255);
				
			} else if (pixel < ug)
			{
				pixel = 0; // schwarz
			} else
			{
				pixel = 255; // weiß in BYTE_GRAY
			}
			pixels[i]= 0xFF|pixel<<16|pixel<<8|pixel;
		}
		return pixels;
	}
 
Zuletzt bearbeitet:

Planich

Aktives Mitglied
2. Tiff in ein temporäres BufferedImage umwandeln (TYPE_CUSTOM).
3. TemporäresBufferedImage in ein TYPE_USHORT_GRAY BufferedImage umwandeln (das hat deine 16 bit so wie du es dir vorstellst)


Ich glaube ich habe die ganze Zeit schon ein TYPE_USHORT_GRAY
wofür brauche ich denn das erste CUSTOM und wofür wandle ich dieses um?

hier entsteht result als TYPE_USHORT_GRAY

Java:
ColorModel cm = img.getColorModel();                
		width = img.getWidth();
		height = img.getHeight();
		WritableRaster raster = cm.createCompatibleWritableRaster(width, height);
		boolean isAlphaPremultiplied = cm.isAlphaPremultiplied();
		Hashtable properties = new Hashtable();
		String[] keys = img.getPropertyNames();
		if (keys!=null) {
			for (int i = 0; i < keys.length; i++) {
				properties.put(keys[i], img.getProperty(keys[i]));
			}
		}               
		BufferedImage result = new BufferedImage(cm, raster, isAlphaPremultiplied, properties);
 
Zuletzt bearbeitet:

Marco13

Top Contributor
Mit dem Original-Tiff (das jetzt nur EINEN 16-bit-Kanal hat) kann man es etwa so machen, wie... ich es schon ca. 100 mal gepostet habe :autsch:

Es muss eigentlich nur die Lade-Routine angepasst werden.

Hier ist jetzt auch nochmal ein "Histogramm-Panel" dabei, und zwei Slider, mit denen man den dazustellenden Helligkeitsbereich einstellen kann. Und wenn man den "Max"-Slider sehr weit runterdreht, sieht man auch die "versteckten" Strukturen in der unteren Hälfte des Bildes, die eigentlich durch die Abbildung auf 8bit Graustufen unsichtbar wären.


Java:
import java.awt.BorderLayout;
import java.awt.Color;
import java.awt.Graphics;
import java.awt.Graphics2D;
import java.awt.GridLayout;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.awt.image.BufferedImage;
import java.awt.image.DataBuffer;
import java.awt.image.DataBufferUShort;
import java.awt.image.RenderedImage;
import java.io.File;
import java.io.IOException;

import javax.imageio.ImageIO;
import javax.swing.JCheckBox;
import javax.swing.JFrame;
import javax.swing.JLabel;
import javax.swing.JPanel;
import javax.swing.JSlider;
import javax.swing.SwingUtilities;
import javax.swing.event.ChangeEvent;
import javax.swing.event.ChangeListener;

import com.sun.media.jai.codec.ImageCodec;
 
 
class ImagePanel extends JPanel
{
    private BufferedImage image;
    
    public void setImage(BufferedImage image)
    {
        this.image = image;
        repaint();
    }
    
    @Override
    protected void paintComponent(Graphics g)
    {
        super.paintComponent(g);
        if (image != null)
        {
            long before = System.nanoTime();
            g.drawImage(image, 0, 0, getWidth(), getHeight(), null);
            long after = System.nanoTime();
            //System.out.println("Painting "+(after-before)/1e6+" ms");
        }
    }
}


class Histogram
{
	public static int[] compute(short input[], int bins)
	{
		int min = -Short.MAX_VALUE;
		int max = Short.MAX_VALUE;
		int result[] = new int[bins];
		for (int i=0; i<input.length; i++)
		{
			float alpha = (float)(input[i]-min)/(max-min+1);
			int bin = (int)(alpha * bins);
			result[bin]++;
		}
		return result;
	}

	public static float[] computeNormalized(short input[], int bins)
	{
		int h[] = compute(input, bins);
		int max = max(h);
		float result[] = new float[bins];
		for (int i=0; i<h.length; i++)
		{
			result[i] = (float)h[i] / max;
		}
		return result;
	}
	
	
	public static int min(short input[])
    {
    	int min = Integer.MAX_VALUE;
        for (int i=0; i<input.length; i++)
        {
            min = Math.min(min, input[i]);
        }
        return min;
    }
    public static int max(short input[])
    {
    	int max = -Integer.MAX_VALUE;
        for (int i=0; i<input.length; i++)
        {
            max = Math.max(max, input[i]);
        }
        return max;
    }
    
	public static int min(int input[])
    {
    	int min = Integer.MAX_VALUE;
        for (int i=0; i<input.length; i++)
        {
            min = Math.min(min, input[i]);
        }
        return min;
    }
    public static int max(int input[])
    {
    	int max = -Integer.MAX_VALUE;
        for (int i=0; i<input.length; i++)
        {
            max = Math.max(max, input[i]);
        }
        return max;
    }
	
}


class HistogramPanel extends JPanel
{
	private float histogram[];
	private int min = 0;
	private int max = 0;
	
	public void setHistogram(float histogram[])
	{
		this.histogram = histogram;
		repaint();
	}
	
	public void setMinMax(int min, int max)
	{
		this.min = min;
		this.max = max;
		repaint();
	}
	
    @Override
    protected void paintComponent(Graphics g)
    {
        super.paintComponent(g);
        if (histogram != null)
        {
        	g.setColor(Color.BLACK);
        	for (int x=0; x<getWidth(); x++)
        	{
        		float alpha = (float)x/getWidth();
        		int bin = (int)(alpha * histogram.length);
        		int y = (int)(histogram[bin]*getHeight());
        		g.drawLine(x, getHeight(), x, getHeight()-y);
        	}
        	
        	g.setColor(Color.RED);
        	
        	float rMinX = (float)(min+Short.MAX_VALUE)/(2 * Short.MAX_VALUE);
        	int minX = (int)(rMinX * getWidth());
        	g.drawLine(minX,  0, minX, getHeight());
        	
        	float rMaxX = (float)(max+Short.MAX_VALUE)/(2 * Short.MAX_VALUE);
        	int maxX = (int)(rMaxX * getWidth());
        	g.drawLine(maxX,  0, maxX, getHeight());
        	
        	System.out.println("rel "+rMinX+" "+rMaxX);

        }
    }
	
}

 
public class ShortToByteImageTest extends JPanel
{
    public static void main(String[] args)
    {
        SwingUtilities.invokeLater(new Runnable()
        {
            @Override
            public void run()
            {
                createAndShowGUI();
            }
        });
    }
    
    public static String nameOf16bitTIFF = "tiff16bit_2.tif";
    
    private ImagePanel outputImagePanel;
    private short shortImageData[];
    
    public ShortToByteImageTest()
    {
        super(new BorderLayout());
        
        final RenderedImage shortImage = obtainImageWithShortBufferFromSomewhere();
        shortImageData = getShortArray(shortImage);

        JPanel p = new JPanel(new GridLayout(1,2));

        outputImagePanel = new ImagePanel();
        p.add(outputImagePanel);

        float histogram[] = Histogram.computeNormalized(shortImageData, 200);
        final HistogramPanel histogramPanel = new HistogramPanel();
        histogramPanel.setHistogram(histogram);
        p.add(histogramPanel);
        
        add(p, BorderLayout.CENTER);
        
        JPanel controlPanel = new JPanel(new GridLayout(1,0));

        JPanel pMin = new JPanel(new BorderLayout());
        final JSlider minSlider = new JSlider(
            -Short.MAX_VALUE, Short.MAX_VALUE, -Short.MAX_VALUE);
        pMin.add(new JLabel("min"), BorderLayout.WEST);
        pMin.add(minSlider, BorderLayout.CENTER);
        controlPanel.add(pMin);
        
        JPanel pMax = new JPanel(new BorderLayout());
        final JSlider maxSlider = new JSlider(
            -Short.MAX_VALUE, Short.MAX_VALUE, Short.MAX_VALUE);
        pMax.add(new JLabel("max"), BorderLayout.WEST);
        pMax.add(maxSlider, BorderLayout.CENTER);
        controlPanel.add(pMax);
        
        add(controlPanel, BorderLayout.SOUTH);
        
        ChangeListener changeListener = new ChangeListener()
        {
            @Override
            public void stateChanged(ChangeEvent e)
            {
                int min = minSlider.getValue();
                int max = maxSlider.getValue();
                updateImage(shortImage.getWidth(), shortImage.getHeight(), min, max);
                histogramPanel.setMinMax(min, max);
            }
        };
        minSlider.addChangeListener(changeListener);
        maxSlider.addChangeListener(changeListener);
        
        int min = Histogram.min(shortImageData);
        int max = Histogram.max(shortImageData);
        System.out.println("min of image is "+min);
        System.out.println("max of image is "+max);
        minSlider.setValue(min);
        maxSlider.setValue(max);
    }
    
    
    private void updateImage(int w, int h, int min, int max)
    {
        long before = System.nanoTime();
        byte bData[] = convertShortToByteArrayClamping(shortImageData, min, max);
        BufferedImage iImage = createImageFromByteArray(w, h, bData);
        long after = System.nanoTime();
        System.out.println("Conversion with min="+min+" max="+max+" took "+(after-before)/1e6+" ms");
        outputImagePanel.setImage(iImage);
    }
 
    
    private static RenderedImage obtainImageWithShortBufferFromSomewhere()
    {
        try {
			RenderedImage ri = ImageCodec.createImageDecoder("TIFF", new File(nameOf16bitTIFF), null).decodeAsRenderedImage();
			//System.out.println(ri.getData().getDataBuffer());
		    return ri;
			
		} catch (IOException e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
			return null;
		}
    }
    
    private static short[] getShortArray(RenderedImage image)
    {
        DataBuffer dataBuffer = image.getData().getDataBuffer();
        DataBufferUShort databufferUShort = (DataBufferUShort)dataBuffer;
        return databufferUShort.getData();
    }

    
    private static byte[] convertShortToByteArrayClamping(short input[], int min, int max)
    {
        byte output[] = new byte[input.length];
        float normalization = 1.0f / (max - min);
        for (int i=0; i<input.length; i++)
        {
        	float value = 0;
        	if (input[i] < min)
        	{
        		value = 0;
        	}
        	else if (input[i] >= max)
        	{
        		value = 1.0f;
        	}
        	else
        	{
        		value = (input[i]-min)*normalization;
        	}
            output[i] = (byte)(value * 256);
        }
        return output;
    }
    
    private static BufferedImage createImageFromByteArray(int w, int h, byte input[]) 
    {   
        int output[] = new int[input.length];
        for (int i = 0; i < input.length; i++) 
        {
            byte v = input[i];
            int a = 0xFF000000;
            int r = (v & 0xFF) << 16; 
            int g = (v & 0xFF) <<  8; 
            int b = (v & 0xFF) <<  0; 
            output[i] = a | r | g | b;
        }
        BufferedImage result = new BufferedImage(w, h, BufferedImage.TYPE_INT_ARGB);
        result.setRGB(0,0, w, h, output, 0, w);
        return result;
    }
    
    private static void createAndShowGUI()
    {
        JFrame f = new JFrame();
        f.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
        f.getContentPane().add(new ShortToByteImageTest());
        f.setSize(1200,800);
        f.setVisible(true);
    }
    
}
 

Planich

Aktives Mitglied
Mit dem Original-Tiff (das jetzt nur EINEN 16-bit-Kanal hat) kann man es etwa so machen, wie... ich es schon ca. 100 mal gepostet habe :autsch:

Es muss eigentlich nur die Lade-Routine angepasst werden.

Hier ist jetzt auch nochmal ein "Histogramm-Panel" dabei, und zwei Slider, mit denen man den dazustellenden Helligkeitsbereich einstellen kann. Und wenn man den "Max"-Slider sehr weit runterdreht, sieht man auch die "versteckten" Strukturen in der unteren Hälfte des Bildes, die eigentlich durch die Abbildung auf 8bit Graustufen unsichtbar wären.

Ja das ist super.
Die Zeit die dein Code braucht ist aber gleich, bzw. fast nochn Tick länger.
Bei der Umrechnung ist irgendwo noch ein kleiner Fehler da spratzelt es doch sehr wenn ich max runterregel. Auf den Code hab ich noch nicht geschaut, aber jetzt ist eh erstmal Feierabend. Morgen hab ich "frei" also nicht wundern, ihr könnt aber gerne noch hier diskutieren, ich les das Freitag :)
 

Marco13

Top Contributor
Wenn du mit "spratzeln" das "ausfransen" meinst: Bei convertShortToByteArrayClamping wird der bereich, der nicht ins ausgewählte Fenster fällt, abgeschnitten. Du kannst diese Methode durch eine ersetzen, bei der es nicht "sprazelt". (Und falls du eine findest, poste sie hier :bae: )
 
S

Spacerat

Gast
'Ne Kleinigkeit ist mir ja sowas von äh peinlich... :oops: Die Verwendung eines Interleaved- statt einem PackedRasters, die beiden verwechsel' ich andauernd.
Wie dem auch sei, ich hab's jetzt mit einem InterleavedRaster gemacht und schon bin ich beim Neusetzen der Grenzen bei konstant 145ms mit grossem PNG-Image, im Gegensatz zu Marco, der es in ca. der Hälfte der Zeit schafft. Das zuletzt gepostete Bild war wieder nur das kleine TIFF, aber egal, meine Version wird daurch weder schneller noch langsamer. Warum Marco schneller ist, könnte vllt. damit zusammenhängen, dass er den Originalpuffer bei jeder Veränderung implizit in ein Bytearray spreizt, dann in ein ARGB Intarray wandelt und anschliessend in das hardwarenaheste Format TYPE_INT_ARGB wandelt, während ich stets bei 16Bit bleibe und die Daten nur zur Ausgabe in ARGB wandele.
Unverständlich dabei:
Marco wandelt jedesmal erst in ein Bytearray, dann in ein Intarray und schliesslich in ein BI, wobei er die Arrays und das BI auch noch ständig neu instanziert und das Intarray letztendlich per setRGB in das BI kopiert. setRGB benötigt ca. 2 "Kanten" mehr, als die von mir verwendete setPixel-Methode.
Ich instanziere mein OwnImage inkl. aller SubKlassen nur einmal und halte die Originaldaten in einem beim ersten Einlesen angelegten BackUp-Intarray (16-Bit-Werte) und dieses kopiere ich bei jeder Veränderung per setPixels in das BI. Bei der Ausgabe werden die einzelnen Pixel implizit von 16 Bit nach ARGB gewandelt. Im Grossen und Ganzen bERt0r's Idee umgesetzt.
Das mein Panel inzwischen auch die Kontraste wie bei Marco hinbekommt, liegt daran, dass ich seine Umrechnungsformel auf mein Format gepatched habe. So sieht's halt richtiger aus. Aber ich hab' ehrlich gesagt keine Idee mehr, warum meine Version langsamer ist. Schliesslich betreibe ich rein vom Gefühl her den geringeren Aufwand. Habe Marcos Version sogar ebenfalls mit dem grossen PNG gefüttert. ???:L
Java:
import com.sun.media.jai.codec.ImageCodec;
import java.awt.BorderLayout;
import java.awt.Container;
import java.awt.Dimension;
import java.awt.Graphics;
import java.awt.Point;
import java.awt.image.BufferedImage;
import java.awt.image.ColorModel;
import java.awt.image.DataBuffer;
import java.awt.image.Raster;
import java.awt.image.RenderedImage;
import java.awt.image.WritableRaster;
import java.io.File;
import java.io.FileNotFoundException;

import javax.swing.JComponent;
import javax.swing.JFrame;
import javax.swing.JSlider;
import javax.swing.event.ChangeEvent;
import javax.swing.event.ChangeListener;


public class OwnPanel
extends JComponent
{
	private static final long serialVersionUID = 1L;

	private final OwnImage img;

	private OwnPanel(OwnImage img)
	{
		super();
		if(img == null) {
			throw new NullPointerException("img may not be null");
		}
		this.img = img;
		Dimension size = new Dimension(img.getWidth(), img.getHeight());
		setMinimumSize(new Dimension(1, 1));
		setPreferredSize(size);
		setSize(size);
	}

	@Override
	protected void paintComponent(Graphics g)
	{
		super.paintComponent(g);
		long start = System.currentTimeMillis();
		g.drawImage(img, 0, 0, getWidth(), getHeight(), null);
		System.out.println("Conversion and repaint took " + (System.currentTimeMillis() - start) + "ms");
	}

	public static void main(String[] args)
	{
		if(args == null || args.length == 0) {
			args = new String[] {"tiff16bit.png"};
		}
		JFrame f = new JFrame("OwnImageTest");
		f.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
		Container cp = f.getContentPane();
		final JSlider upper = new JSlider(JSlider.VERTICAL);
		final JSlider lower = new JSlider(JSlider.VERTICAL);
		OwnImage oi = new OwnImage(new File(args[0]).getAbsoluteFile());
		final OwnPanel op = new OwnPanel(oi);
		cp.setLayout(new BorderLayout());
		cp.add(op, BorderLayout.CENTER);
		cp.add(upper, BorderLayout.EAST);
		cp.add(lower, BorderLayout.WEST);
		upper.setMaximum(0xFFFF);
		upper.setMinimum(0);
		upper.setValue(oi.getOgDraw());
		upper.addChangeListener(new ChangeListener()
		{
			@Override
			public void stateChanged(ChangeEvent e)
			{
				int v = upper.getValue();
				op.img.setOgDraw(v);
				lower.setMaximum(v);
				op.repaint();
			}
		});
		lower.setMaximum(0xFFFF);
		lower.setMinimum(0);
		lower.setValue(oi.getUgDraw());
		lower.addChangeListener(new ChangeListener()
		{
			@Override
			public void stateChanged(ChangeEvent e)
			{
				int v = lower.getValue();
				op.img.setUgDraw(v);
				upper.setMinimum(v);
				op.repaint();
			}
		});
		f.pack();
		f.setVisible(true);
	}
}

class OwnImage
extends BufferedImage
{
	private final int[] backUp;

	public OwnImage(File f)
	{
		this(new Loader(f));
	}

	private OwnImage(Loader l)
	{
		super(l.cm, l.data, false, null);
		backUp = l.data.getPixels(0, 0, getWidth(), getHeight(), (int[]) null);
	}

	public synchronized int getUgDraw()
	{
		return ((OwnColorModel) getColorModel()).ug;
	}

	public synchronized int getOgDraw()
	{
		return ((OwnColorModel) getColorModel()).og;
	}

	public synchronized void setUgDraw(int ug)
	{
		((OwnColorModel) getColorModel()).setUg(ug);
		restore();
	}

	public synchronized void setOgDraw(int og)
	{
		((OwnColorModel) getColorModel()).setOg(og);
		restore();
	}

	public synchronized void setUgOgDraw(int ug, int og)
	{
		((OwnColorModel) getColorModel()).setBorders(ug, og);
		restore();
	}

	@Override
	public void setRGB(int startX, int startY, int w, int h, int[] rgbArray, int offset, int scansize)
	{
		int yoff  = offset;
		int off, rgb;
		short[] pixel = null;

		for(int y = startY; y < startY + h; y++, yoff+=scansize) {
			off = yoff;
			for(int x = startX; x < startX + w; x++) {
				pixel = (short[]) getColorModel().getDataElements(rgbArray[off++], pixel);
				rgb = pixel[0] & 0xFFFF;
				backUp[y * getWidth() + x] = rgb;
				getRaster().setDataElements(x, y, pixel);
			}
                }
	}

	@Override
	public synchronized void setRGB(int x, int y, int rgb)
	{
		short[] pi = (short[]) getColorModel().getDataElements(rgb, null);
		rgb = pi[0] & 0xFFFF;
		backUp[y * getWidth() + x] = rgb;
		getRaster().setDataElements(x, y, pi);
	}

	private void restore()
	{
		getRaster().setPixels(0, 0, getWidth(), getHeight(), backUp);
	}

	private static class Loader
	{
		private ColorModel cm;
		private WritableRaster data;

		private Loader(File f)
		{
			RenderedImage ri = null;
			try {
                //Rendered Image erstellen
                ri = ImageCodec.createImageDecoder("TIFF", f, null).decodeAsRenderedImage();
			} catch(FileNotFoundException e) {
				e.printStackTrace();
				return;
			}
			int min = Integer.MAX_VALUE;
			int max = Integer.MIN_VALUE;
			int w = ri.getWidth();
			int h = ri.getHeight();
			data = Raster.createPackedRaster(DataBuffer.TYPE_USHORT, w, h, 1, 16, new Point());
			Raster raster = ri.getData();
			ColorModel model = ri.getColorModel();
			Object buffer = null;
			int numComponents = model.getNumComponents();
			int type = model.getTransferType();
			int red = 0, green = 0, blue = 0;
			switch(type) {
			case DataBuffer.TYPE_BYTE:
				buffer = new byte[numComponents];
				break;
			case DataBuffer.TYPE_SHORT:
			case DataBuffer.TYPE_USHORT:
				buffer = new short[numComponents];
				break;
			case DataBuffer.TYPE_INT:
				buffer = new int[numComponents];
				break;
			default:
				throw new UnsupportedOperationException("unsupported tranfer type");
			}
			int c = max;
			for(int x = 0; x < w; x++) {
				for(int y = 0; y < h; y++) {
					raster.getDataElements(x, y, buffer);
					if(type != DataBuffer.TYPE_SHORT && type != DataBuffer.TYPE_USHORT) {
						red = model.getRed(buffer) << 8;
						green = model.getGreen(buffer) << 8;
						blue = model.getBlue(buffer) << 8;
					} else {
						if(numComponents == 1) {
							c = ((short[]) buffer)[0] & 0xFFFF;
						} else {
							red = ((short[]) buffer)[0] & 0xFFFF;
							green = ((short[]) buffer)[1] & 0xFFFF;
							blue = ((short[]) buffer)[2] & 0xFFFF;
						}
					}
					if(red != green || red != blue || green != blue) {
						red = (int) (red * 0.3 + green * 0.59 + blue * 0.11);
					}
					c = red & 0xFFFF;
					data.setSample(x, y, 0, c);
					if(min > c) {
						min = c;
					}
					if(max < c) {
						max = c;
					}
				}
			}
			cm = new OwnColorModel(min, max);
		}
	}

	private static class OwnColorModel
	extends ColorModel
	{
		private int og, ug;
		private double ratio;

		public OwnColorModel(int ug, int og)
		{
			super(8);
			setBorders(ug, og);
		}

		@Override
		public boolean isCompatibleRaster(Raster raster)
		{
			return true;
		}

		@Override
		public int getRed(int pixel)
		{
			return pixel2RGB(pixel);
		}

		@Override
		public int getGreen(int pixel)
		{
			return pixel2RGB(pixel);
		}

		@Override
		public int getBlue(int pixel)
		{
			return pixel2RGB(pixel);
		}

		@Override
		public int getAlpha(int pixel)
		{
			return 0xFF;
		}

		@Override
		public int getRGB(Object inData)
		{
			if(inData == null) {
				throw new NullPointerException("inData may not be null");
			}
			if(inData instanceof byte[]) {
				return pixel2RGB(((byte[]) inData)[0]);
			} else if(inData instanceof short[]) {
				return pixel2RGB(((short[]) inData)[0]);
			} else if(inData instanceof int[]) {
				return pixel2RGB(((int[]) inData)[0]);
			} else {
				return 0;
			}
		}

		@Override
		public int getRGB(int pixel)
		{
			return pixel2RGB(pixel);
		}

		@Override
		public Object getDataElements(int rgb, Object pixel)
		{
			if((rgb & 0xFFFF) != Math.abs(rgb)) {
				int r = (rgb >> 16) & 0xFF;
				int g = (rgb >>  8) & 0xFF;
				int b = (rgb >>  0) & 0xFF;
				if(r != g || r != b || g != b) {
					r = (int) (r * 0.3 + g * 0.59 + b * 0.11) & 0xFF;
				}
				rgb = r << 8 | r;
			}
			if(pixel == null) {
				pixel = new short[1];
			}
			if(pixel instanceof byte[]) {
				byte[] b = (byte[]) pixel;
				b[0] = (byte) rgb;
				if(b.length > 1) {
					throw new IllegalArgumentException("no more components");
				}
			} else if(pixel instanceof short[]) {
				short[] b = (short[]) pixel;
				b[0] = (short) rgb;
				if(b.length > 1) {
					throw new IllegalArgumentException("no more components");
				}
			}
			if(pixel instanceof int[]) {
				int[] b = (int[]) pixel;
				b[0] = rgb;
				if(b.length > 1) {
					throw new IllegalArgumentException("no more components");
				}
			}
			return pixel;
		}

		private void setUg(int ug)
		{
			setBorders(ug, og);
		}

		private void setOg(int og)
		{
			setBorders(ug, og);
		}

		private void setBorders(int ug, int og)
		{
			og &= 0xFFFF;
			ug &= 0xFFFF;
			this.ug = Math.min(ug, og);
			this.og = Math.max(ug, og);
			ratio = 65535.0 / (this.og - this.ug);
		}

		private final int pixel2RGB(int pixel)
		{
			pixel &= 0xFFFF;
			pixel = ((int) ((pixel - ug) * ratio) >> 8) & 0xFF;
			return pixel | pixel << 8 | pixel << 16 | 0xFF000000;
		}
	}
}
 

Marco13

Top Contributor
Unverständlich dabei:
Marco wandelt jedesmal erst in ein Bytearray, dann in ein Intarray und schliesslich in ein BI, wobei er die Arrays und das BI auch noch ständig neu instanziert und das Intarray letztendlich per setRGB in das BI kopiert. setRGB benötigt ca. 2 "Kanten" mehr, als die von mir verwendete setPixel-Methode.

Was meinst du mit "Kanten"? BTW: Irgendwann zwischendrin hatte ich auch mal eine Version, wo direkt aus dem Short-Array der RGB-int-array gemacht wurde (ohne den byte-Zwischenschrit). Aber weil lange nicht klar war, welches Format der short-Array hat (48bit oder 16bit) und das auf die Konvertierung in den byte-Array Einfluß hat, habe ich die Schritte dann doch mal getrennt.
(BTW: Diesen Einfluß gibt es bei dir ja nicht, oder? Habe es aber nicht soooo im Detail nachvollzogen...)
 
S

Spacerat

Gast
Was meinst du mit "Kanten"?
Damit meine ich weitere Methodenaufrufe und darin enthaltene Anweisungen inklusive erstellen von Transferarrays.
BTW: Irgendwann zwischendrin hatte ich auch mal eine Version, wo direkt aus dem Short-Array der RGB-int-array gemacht wurde (ohne den byte-Zwischenschrit). Aber weil lange nicht klar war, welches Format der short-Array hat (48bit oder 16bit) und das auf die Konvertierung in den byte-Array Einfluß hat, habe ich die Schritte dann doch mal getrennt.
(BTW: Diesen Einfluß gibt es bei dir ja nicht, oder? Habe es aber nicht soooo im Detail nachvollzogen...)
Diesen Einfluss gibt es bei mir tatsächlich nicht, ich kann beliebige Formate lesen. Wenn der Transfertyp != Short oder uShort ist, werden nacheinander jeweils 8-Bit getRed, -Green und -Blue aufgreufen, bei den beiden anderen wird die Anzahl der Componenten in ein short-Array geladen. R, G und B werden dann auf Gleichheit geprüft und ggf. linear auf 16-Bit grayScaled.
... noch 'ne Kleinigkeit zum Code... Zeile 227 muss nochmal geändert werden.
[JAVA=227] red = green = blue = ((short[]) buffer)[0] & 0xFFFF;[/code]Da ist noch Restcode von meinen Experimenten mit den hier verwendeten Bildern übergeblieben. Und da nun eh' nur wieder TIFF verwendet werden kann, kann man die Konvertierungen eigentlich auch wieder entfernen.
[EDIT]Selbst wenn ich in der Restore-Methode [c]setRGB(0, 0, getWidth(), getHeight(), backUp, 0, getWidth());[/c] statt setPixels verwende, wird's nicht schneller. :([/EDIT]
[EDIT]#2
Was mir grad' noch auffällt... Ich kann die Konvertierungszeit gar nicht separieren... Marco tut dies und man kann zu seinen ca. 80ms noch mal 10ms für's Painten hinzuzählen... ist aber trotzdem noch schneller.[/EDIT]
 
Zuletzt bearbeitet von einem Moderator:

bERt0r

Top Contributor
Falls es noch interessiert, ich hab mal einiges über den haufen geworfen und nochmal optimiert. Ich schreibe die Daten jetzt einfach in den Raster des Images und mache keine Umwandlungen in RGB oder dergleichen. Ich glaube das war auch die ursprüngliche Idee von Planich, ich weis jetzt aber auch nicht wo er seine Performance versenkt hat. Bei mir auf meinem schwachbrüstigen Laptop läuft das jetzt mit dem Beispielbild aus dem Zip in 10 ms für die Berechnung und 1 ms zum Zeichnen.
Und es funzt mit allen Bildern :)

Java:
import java.awt.BorderLayout;
import java.awt.EventQueue;
import java.awt.Graphics;
import java.awt.GridLayout;
import java.awt.Image;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.awt.image.BufferedImage;
import java.awt.image.ColorModel;
import java.awt.image.DataBufferUShort;
import java.awt.image.RenderedImage;
import java.awt.image.WritableRaster;
import java.io.File;
import java.lang.reflect.Field;
import java.util.Hashtable;

import javax.media.jai.JAI;
import javax.swing.ImageIcon;
import javax.swing.JButton;
import javax.swing.JFileChooser;
import javax.swing.JFrame;
import javax.swing.JLabel;
import javax.swing.JPanel;
import javax.swing.JScrollPane;
import javax.swing.JSlider;
import javax.swing.JTextField;
import javax.swing.SwingConstants;
import javax.swing.border.EmptyBorder;
import javax.swing.event.ChangeEvent;
import javax.swing.event.ChangeListener;

public class GraustufenRegler extends JFrame
{
	short upperBound,lowerBound;
	private JPanel contentPane;
	private JFileChooser fileChooser;
	private BufferedImage image;
	private JScrollPane scrollPane;
	private JLabel lblType;
	private JLabel lblImage;
	private JPanel sliderPanel;
	private JPanel botPanel;
	private JLabel lblUpperbound;
	private JLabel lblLowerbound;
	private JTextField textFieldLower;
	private JTextField textFieldUpper;
	private JSlider sliderUpper;
	private JSlider sliderLower;
	private short[] shortData;
	
	/**
	 * Launch the application.
	 */
	public static void main(String[] args)
	{
		EventQueue.invokeLater(new Runnable()
			{
				public void run()
				{
					try
					{
						GraustufenRegler frame = new GraustufenRegler();
						frame.setVisible(true);
					} catch (Exception e)
					{
						e.printStackTrace();
					}
				}
			});
	}
	
	/**
	 * Create the frame.
	 */
	public GraustufenRegler()
	{
		setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
		setBounds(100, 100, 600, 600);
		contentPane = new JPanel();
		contentPane.setBorder(new EmptyBorder(5, 5, 5, 5));
		contentPane.setLayout(new BorderLayout(0, 0));
		setContentPane(contentPane);
		
		sliderPanel = new JPanel();
		contentPane.add(sliderPanel, BorderLayout.EAST);
		sliderPanel.setLayout(new GridLayout(0, 2, 0, 0));
		
		sliderUpper = new JSlider();
		sliderUpper.setMaximum(Short.MAX_VALUE);
		sliderUpper.setMinimum(Short.MIN_VALUE);
		sliderUpper.setOrientation(SwingConstants.VERTICAL);
		sliderPanel.add(sliderUpper);
		
		sliderUpper.addChangeListener(new ChangeListener() {
			public void stateChanged(ChangeEvent e) 
			{
				upperBound = (short) sliderUpper.getValue();
				textFieldUpper.setText(String.valueOf(upperBound));
				updateImage();
			}
		});
		
		
		sliderLower = new JSlider();
		sliderLower.setMaximum(Short.MAX_VALUE);
		sliderLower.setMinimum(Short.MIN_VALUE);
		sliderLower.setOrientation(SwingConstants.VERTICAL);
		sliderPanel.add(sliderLower);
		
		sliderLower.addChangeListener(new ChangeListener() {
			public void stateChanged(ChangeEvent e) 
			{
				lowerBound = (short) sliderLower.getValue();
				textFieldLower.setText(String.valueOf(lowerBound));
				updateImage();
			}
		});;
		
		botPanel = new JPanel();
		contentPane.add(botPanel, BorderLayout.SOUTH);
		
		JButton btnLoadImage = new JButton("Load Image");
		botPanel.add(btnLoadImage);
		
		lblLowerbound = new JLabel("LowerBound:");
		botPanel.add(lblLowerbound);
		
		textFieldLower = new JTextField();
		textFieldLower.setEditable(false);
		botPanel.add(textFieldLower);
		textFieldLower.setColumns(5);
		
		lblUpperbound = new JLabel("UpperBound:");
		botPanel.add(lblUpperbound);
		
		textFieldUpper = new JTextField();
		textFieldUpper.setEditable(false);
		botPanel.add(textFieldUpper);
		textFieldUpper.setColumns(5);
		
		scrollPane = new JScrollPane();
		contentPane.add(scrollPane, BorderLayout.CENTER);
		
		lblType = new JLabel("");
		lblType.setHorizontalAlignment(SwingConstants.CENTER);
		scrollPane.setColumnHeaderView(lblType);
		
		lblImage = new JLabel()
		{
			@Override
			public void paintComponent(Graphics g)
			{
				long start = System.nanoTime();
				super.paintComponent(g);
				long end = System.nanoTime();
				System.out.println("Time to paint: " + (end - start)/10000000d + " ms");
			}
		};
		lblImage.setHorizontalAlignment(SwingConstants.CENTER);
		scrollPane.setViewportView(lblImage);
		
		btnLoadImage.addActionListener(new ActionListener()
			{
				public void actionPerformed(ActionEvent arg0)
				{
					int ret = fileChooser.showOpenDialog(GraustufenRegler.this);
					if (ret == JFileChooser.APPROVE_OPTION)
					{
						File file=fileChooser.getSelectedFile();
						image= loadImage(file);
						
						if (image != null)
						{
							image = convertImage(image,BufferedImage.TYPE_USHORT_GRAY);
							
							shortData=((DataBufferUShort)image.getData().getDataBuffer()).getData();
							lblType.setText(file.getName()+" "+getTypeString(image));
							lblImage.setIcon(new ImageIcon(image));
							
							calculateMinMax(shortData);
						}
					}
				}
			});
		
		fileChooser = new JFileChooser();
	}
	
	public BufferedImage convertImage(Image img, int type)
	{
		BufferedImage ret = new BufferedImage(img.getWidth(null), img.getHeight(null), type);
		Graphics g = ret.getGraphics();
		g.drawImage(img, 0, 0, null);
		g.dispose();
		return ret;
	}
		
	
	private short[] processPixels(short[] imgData, short upper, short lower)
	{
		int ug = Math.min(upper,lower);
		int og = Math.max(upper,lower);
		double spektrum=(og-ug);
		short[] pixels=new short[imgData.length];
		int pixel;
		
		for (int i = 0; i < imgData.length; i++)
		{
			pixel=imgData[i];
			
			if (pixel >= ug && pixel <= og)
			{
				pixel = (short) (((pixel - ug)/spektrum)*2*Short.MAX_VALUE);
				
			} else if (pixel < ug)
			{
				pixel = 0;
			} else
			{
				pixel = -1;
			}
			pixels[i]= (short) pixel;
		}
		return pixels;
	}
	
	private String getTypeString(BufferedImage img)
	{
		int type = img.getType();
		for (Field f : BufferedImage.class.getDeclaredFields())
		{
			if (f.getName().startsWith("TYPE"))
			{
				try
				{
					if (f.getInt(img) == type)
					{
						return f.getName();
					}
				} catch (IllegalArgumentException e)
				{
					e.printStackTrace();
				} catch (IllegalAccessException e)
				{
					e.printStackTrace();
				}
			}
		}
		return "Not found";
	}
	
	private synchronized void updateImage()
	{
		long start=System.nanoTime();
		if (image != null)
		{
			int w = image.getWidth();
			int h = image.getHeight();
			
			short[] pixelData=processPixels(shortData, lowerBound, upperBound);
			image.getRaster().setDataElements(0, 0,w,h, pixelData);
			lblImage.repaint();
		}
		long end=System.nanoTime();
		System.out.println("Time to calculate: "+(end-start)/1000000d+" ms");
	}
	
	private BufferedImage loadImage(File f)
	{
		RenderedImage rImage=JAI.create("fileload",f.getAbsolutePath());
		
		ColorModel cm = rImage.getColorModel();                
        int width = rImage.getWidth();
        int height = rImage.getHeight();
        WritableRaster raster = cm.createCompatibleWritableRaster(width, height);
        boolean isAlphaPremultiplied = cm.isAlphaPremultiplied();
        Hashtable<String,Object> properties = new Hashtable<String,Object>();
        String[] keys = rImage.getPropertyNames();
        if (keys!=null) 
        {
            for (int i = 0; i < keys.length; i++) {
                properties.put(keys[i], rImage.getProperty(keys[i]));
            }
        }
        rImage.copyData(raster);
        BufferedImage result = new BufferedImage(cm, raster, isAlphaPremultiplied, properties);
        return result;
	}
	
	private void calculateMinMax(short[] arr)
	{
		short min=Short.MAX_VALUE;
		short max=Short.MIN_VALUE;
		
		for(int i=0;i<arr.length;i++)
		{
			if(arr[i]<min)
			{
				min=arr[i];
			}
			else if(arr[i]>max)
			{
				max=arr[i];
			}
		}		
		sliderUpper.setMinimum(min);
		sliderUpper.setMaximum(max);
		sliderUpper.setValue(max);
			
		sliderLower.setMinimum(min);
		sliderLower.setMaximum(max);
		sliderLower.setValue(min);
	}
}
 
S

Spacerat

Gast
@bERt0r: Gut gemacht... Aber hast du dir mal die Anwendungen von Marco und mir angesehen? Da kommt bei dem grossen PNG-Bild im mittleren Abschnitt bei einigen vielen Einstellungen der Regler so ein Gebilde zum Vorschein, dass an "Fischgräten" erinnert (siehe Anhang). Bei deiner Version kann ich einstellen, was ich will, das Gebilde will sich nicht zeigen.
 

Anhänge

  • Fischgräten.jpg
    Fischgräten.jpg
    97,5 KB · Aufrufe: 24
Zuletzt bearbeitet von einem Moderator:

Planich

Aktives Mitglied
Falls es noch interessiert, ich hab mal einiges über den haufen geworfen und nochmal optimiert. Ich schreibe die Daten jetzt einfach in den Raster des Images und mache keine Umwandlungen in RGB oder dergleichen. Ich glaube das war auch die ursprüngliche Idee von Planich, ich weis jetzt aber auch nicht wo er seine Performance versenkt hat. Bei mir auf meinem schwachbrüstigen Laptop läuft das jetzt mit dem Beispielbild aus dem Zip in 10 ms für die Berechnung und 1 ms zum Zeichnen.
Und es funzt mit allen Bildern :)

Meine Performance versenkt? Ich habe hier gerade noch die performanteste Lösung denke ich :)

Ich benutze hier auch die ganze Zeit mein 6MB Tiff, welches ich hier leider nicht hochladen kann. Wie gesagt, ich kanns euch mailen, dann schickt einfach nur ne PM an mich.


Haut mich, aber statt set RGB, könnt ihr doch lieber mit dem int[]RGB ein IMG erstellen und das in die Graphics eures BufferedImages zeichnen. Ich bilde mir ein, das war bei meinen Tests schneller.

Was ich noch nicht ganz nachvollziehen konnte, und jetzt die nächsten Stunden mal versuche ist, dass ihr nicht wie ich, son komischen Kram hier machen müsst

Java:
public byte[] shortToByte(short[] data, int ug, int og)
{           t2=System.currentTimeMillis();
            
            byte[] output=new byte[byteArr.length];
            //Berechnen der Hälfte - Schnittpunkt Hälfte ist weiß und schwarz
            short half=(short)(((og-ug)>>1));
                       
            //float ratio = (og-ug)/256;
            short ratio =(short)( (og-ug));
            
            
            //-32768 ist grau|| -1 weiß|| 0 schwarz || 32767 grau 
            for (int i=0;i<data.length;i++)
            {
                if(data[i]>=ug&&data[i]<=og)
                {
                    //Schwarzstufen
                    if(data[i]<og-(half+1))                    
                    {                            
                        output[i]=(byte)(((data[i]-ug)<<8)/ratio);                     
                    }
                    //Weißstufen     
                    else
                    {                    
                        output[i]=(byte)(((data[i]-ug)<<8)/ratio-256);
                        //Falls Rundungsfehler (-0.3 wäre 0 und damit schwarz), manuelles setzen auf -1 ->entspricht weiß
                        if(output[i]==0)
                        {
                            output[i]=-1;
                        }
                    }
                }
                if(data[i]<ug)
                    output[i]=0;//schwarz
                if(data[i]>og)
                    output[i]=-1;//weiß  
            }
            byteArr=output;
           
            return output;
}

soll heißen, bei mir verläuft schwarz nach weiß nicht von min nach max, sondern die hälfte ist bei max.
Wenn ich mir da die vergleiche sparen könnte und sone lineare Berechnung machen könnte, wäre es wahrscheinlich noch schneller.

Wenn du mit "spratzeln" das "ausfransen" meinst: Bei convertShortToByteArrayClamping wird der bereich, der nicht ins ausgewählte Fenster fällt, abgeschnitten. Du kannst diese Methode durch eine ersetzen, bei der es nicht "sprazelt". (Und falls du eine findest, poste sie hier :bae: )

Sieh mal die zwei Bilder im Anhang, dann siehst du was ich mit spratzeln meine

meinen wir jetzt mit spratzeln das gleiche?
Kann es sein, dass du einfach mit der falschen Farbe den "Rest" ausblendest? Also das was weiß sein müsste mit schwarz und umgekehrt?
 

Anhänge

  • marcos.PNG
    marcos.PNG
    83 KB · Aufrufe: 18
  • meins.jpg
    meins.jpg
    68,5 KB · Aufrufe: 21
Zuletzt bearbeitet:
Ähnliche Java Themen
  Titel Forum Antworten Datum
H Transparent zeichnen mit drawImage in paintComponent Methode AWT, Swing, JavaFX & SWT 3
J drawImage Fehlersuche AWT, Swing, JavaFX & SWT 5
U drawImage mit EPS AWT, Swing, JavaFX & SWT 0
A Problem mit drawImage AWT, Swing, JavaFX & SWT 1
M Graphics.drawImage von unten nach oben abbilden lassen AWT, Swing, JavaFX & SWT 6
L Graphics.drawImage() - Output-Größe entspricht nicht Parametern AWT, Swing, JavaFX & SWT 10
L Border verschwindet durch Graphics.drawImage() AWT, Swing, JavaFX & SWT 4
P Swing Skalieren mit DrawImage macht Linien kaputt AWT, Swing, JavaFX & SWT 6
G .ico drawImage AWT, Swing, JavaFX & SWT 5
B drawImage funktioniert nicht AWT, Swing, JavaFX & SWT 4
B drawImage auf JPanel bleibt ohne Auswirkungen AWT, Swing, JavaFX & SWT 9
K Graphics.drawImage() sehr schnell AWT, Swing, JavaFX & SWT 5
M Graphics.drawImage verlangsamt sich plötzlich AWT, Swing, JavaFX & SWT 15
0 AWT Graphics2D.drawImage() funktioniert nicht mehr korrekt mit Core i7 AWT, Swing, JavaFX & SWT 4
G Graphics.drawImage() AWT, Swing, JavaFX & SWT 6
? Problem mit drawImage: bei Frame ok, bei JPanel nicht AWT, Swing, JavaFX & SWT 4
F Problem mit drawImage() AWT, Swing, JavaFX & SWT 6
M drawImage bremst GUI AWT, Swing, JavaFX & SWT 2
I drawImage AWT, Swing, JavaFX & SWT 4
B drawImage() hängt! AWT, Swing, JavaFX & SWT 18
O performance g2d.drawImage() AWT, Swing, JavaFX & SWT 17
L Bildbewegung mit g.drawImage AWT, Swing, JavaFX & SWT 3
K g.DrawImage unter paintComponent klappt nur beim 1. Aufruf AWT, Swing, JavaFX & SWT 3
S kurze Frage zu drawImage AWT, Swing, JavaFX & SWT 12
F Endlosschleife bei drawImage() AWT, Swing, JavaFX & SWT 4
L Gezeichnetes Image mit DrawImage überzeichnen AWT, Swing, JavaFX & SWT 3
M drawImage mit seltsamen verhalten AWT, Swing, JavaFX & SWT 2
D graphische Ausgabe zu langsam (vsync gzielt abschaltbar?)... AWT, Swing, JavaFX & SWT 13
E Java-TexturePaint sehr langsam AWT, Swing, JavaFX & SWT 9
Tommy135 JFileChooser ist sehr langsam AWT, Swing, JavaFX & SWT 13
M Swing GUI wird nach invokeLater() langsam AWT, Swing, JavaFX & SWT 19
D JavaFX GUI Komponenten werden langsam bei größerer Datenmenge AWT, Swing, JavaFX & SWT 6
J JavaFX Rendering von Canvas sehr langsam AWT, Swing, JavaFX & SWT 2
C Swing GUI extrem langsam - GUI-Code richtig ausführen AWT, Swing, JavaFX & SWT 1
L [Slick2d] Sidescroller/Hintergrundbild sehr langsam AWT, Swing, JavaFX & SWT 3
S Swing JtextPane sau langsam AWT, Swing, JavaFX & SWT 15
P JFrame langsam / seltsames Verhalten AWT, Swing, JavaFX & SWT 6
X JInternalFrame vor Java2D-Zeichnung langsam bzw. Gui friert ein AWT, Swing, JavaFX & SWT 1
H Swing JScrollPane mit "viel Inhalt" scrollt zu langsam (inkl. See-For-Yourself.jar :D) AWT, Swing, JavaFX & SWT 2
D Image soll langsam sichtbar werden AWT, Swing, JavaFX & SWT 4
M JTable mit wechselnden Spalten - sehr Langsam AWT, Swing, JavaFX & SWT 5
A HELP: JFieldText dynamisch setzen -> langsam AWT, Swing, JavaFX & SWT 19
O RandomAccesFile langsam AWT, Swing, JavaFX & SWT 6
lumo AWT Screenshots machen ist langsam? AWT, Swing, JavaFX & SWT 6
J JApplet langsam wegen vielen Tooltips? AWT, Swing, JavaFX & SWT 36
R Image laden sehr langsam AWT, Swing, JavaFX & SWT 7
F Swing JTable langsam AWT, Swing, JavaFX & SWT 13
Kr0e VolatileImage langsam AWT, Swing, JavaFX & SWT 10
A repaint() zu langsam, bitte um alternativen AWT, Swing, JavaFX & SWT 5
A Swing JTextPane sehr langsam AWT, Swing, JavaFX & SWT 6
R TableRowSorter... zu langsam AWT, Swing, JavaFX & SWT 9
Stillmatic JTextPane langsam? AWT, Swing, JavaFX & SWT 5
R JTable für sehr viele Daten sehr langsam AWT, Swing, JavaFX & SWT 20
PAX JList aktualisiert zu langsam beim Hinzufügen von Einträgen AWT, Swing, JavaFX & SWT 6
G JScrollPane scrollt zu langsam AWT, Swing, JavaFX & SWT 6
S Bilder werden sehr langsam geladen AWT, Swing, JavaFX & SWT 4
M jFileChooser extrem langsam AWT, Swing, JavaFX & SWT 15
G Swing Programmstart zu langsam AWT, Swing, JavaFX & SWT 3
J JFileChooser öffnet sich in manchen Fällen extrem langsam! AWT, Swing, JavaFX & SWT 12
D Scrollbalken zu langsam AWT, Swing, JavaFX & SWT 10
S Programm aufgrund von paint() zu langsam AWT, Swing, JavaFX & SWT 18
doctus img.getScaledInstance() sehr rechenintensiv und langsam? AWT, Swing, JavaFX & SWT 3
T Linie langsam zeichnen AWT, Swing, JavaFX & SWT 3
C JButton + JFrame Reaktion SEHR langsam. AWT, Swing, JavaFX & SWT 2
J Double-Buffering zu langsam AWT, Swing, JavaFX & SWT 4
A Warum ist jtable.addRowSelectionIntervall so langsam? AWT, Swing, JavaFX & SWT 10
T Swing bei Realtime-Aktualisierung zu langsam? AWT, Swing, JavaFX & SWT 10
C TreeModel zu langsam für EventDispatchThread AWT, Swing, JavaFX & SWT 5

Ähnliche Java Themen


Oben