Innholdsoversikt for programutvikling

Litt om bilder

Java har omfattende muligheter for behandling av bilder. I dette notatet blir bare det mest grunnleggende omtalt.

Et bilde som vises på dataskjermen er bygget opp av et stort antall små bildeelementer. Et slikt bildeelement kalles en piksel, på engelsk pixel, som er forkortelse for picture element. På skjermen vil en slik piksel være et lite kvadrat, så lite at vi ikke kan se det med det blotte øye. Hver slik piksel har en bestemt farge og en bestemt lysstyrke. Bildefiler inneholder opplysninger om farge og lysstyrke for de enkelte pikslene som bildet består av. Dette kan bli store datamengder og dermed resultere i store filer. For å redusere filstørrelsen blir det som regel brukt forskjellige komprimeringsteknikker. Avhengig av hvilken komprimeringsteknikk som er brukt, får vi da forskjellige typer bildefiler. Den vanligste typen for fotografier er den som har bildefiler med filnavn som slutter på .jpg eller .jpeg. Det er en komprimeringsteknikk som gir kvalitetsforringelse i forhold til originalbildet som blir komprimert. Hensikten er at dette skal skje på en måte som det menneskelige øyet ikke kan oppdage. Men dersom vi foretar en eller annen manipulering av et jpg-bilde og lagrer det nye bildet, er det ikke mulig ut fra den nye fila å få tilbake kvaliteten i originalbildet, så sørg for å ta vare på originalbildene.

En annen nettstandard for bilder er det som kalles gif-bilder. Bildefiler for slike har filnavn som slutter med .gif. Gif-bilder tillater høyst 256 forskjellige farger, mens jpg-bilder tillater inntil 16,7 millioner. Til gjengjeld gir gif-bilder komprimering uten kvalietetsforringelse. Gif-formatet egner seg for logoer, strektegninger og ikoner. En annen standard som har samme egenskaper som gif, er png-standarden. Den ble utviklet som et patentfritt alternativ til gif-formatet, men det representerer også noen forbedringer på den måten at filstørrelsen blir 5 til 25 prosent mindre enn for et tilsvarende gif-bilde. Java støtter alle de tre nevnte filformatene.

I java-sammenheng kan et bilde enten være et ImageIcon-objekt eller et Image-objekt. Egentlig bruker et ImageIcon-objekt et Image-objekt til å lagre sine bildedata i.

Et ImageIcon-objekt er et bilde av fast størrelse. Det blir vanligvis oppfattet som et lite bilde, siden det er slike som brukes som dekorasjon på for eksempel knapper. Det er imidlertid ingen begrensning på størrelsen.

Opprette bilde i en applikasjon

Det er litt forskjell på å opprette et ImageIcon-bilde avhengig av om det skjer i en applikasjon eller i en applet. I en applikasjon blir det anbefalt å gjøre det på denne måten (uttrykt i pseudo-kode), i tilfelle bildefilene tilhører applikasjonen:

  ImageIcon bilde = null;
  java.net.URL kilde = Klasse.class.getResource( < filnavn eller filsti > );

  if ( kilde != null )
    bilde = new ImageIcon( kilde );
  else
    < passende feilmelding >

Det som ovenfor er kalt Klasse skal være navn på den klasse som instruksjonene står i. Det er mulig å opprette et bilde direkte ved å bruke filnavnet for bildefila som konstruktørparameter. Men ved å gjøre det som vist ovenfor får man først sjekket om bildefila virkelig er tilgjengelig, og får mulighet for å gi passende feilmelding dersom det ikke skulle være tilfelle.

Tegne et ImageIcon-bilde

Et ImageIcon-bilde har evne til å tegne seg selv ved bruk av metoden paintIcon:

  bilde.paintIcon(tegneflate, grafikkontekst, x, y);

Tegneflata vil vanligvis være et panel som ikke skal ha andre oppgaver enn nettopp å vise vedkommende bilde. Det som er kalt grafikkontekst er den såkalte grafikk-konteksten til tegneflata, det vil si det Graphics-objekt som er knyttet til tegneflata og som blant annet er parameter i tegneflatas paint- eller paintComponent-metode. Parametrene x og y angir koordinatene til bildets øverste venstre hjørne relativt til tegneflata.

Programeksempel 1

Programmet Ikontest.java som er gjengitt nedenfor tegner et bilde på et JPanel i et JFrame-vindu. Bildefila Ipeint01.jpg forutsettes å ligge i en underkatalog bildefiler under katalogen som inneholder klassefilene til programmet. Tegnepanelet blir gitt samme størrelse som bildet. Dimensjonene til dette hentes ut ved bruk av metodene getIconWidth og getIconHeight.

 1 import java.awt.*;
 2 import javax.swing.*;
 3 import java.net.URL;
 4
 5 //Viser et bilde i form av et ImageIcon i et JFrame-vindu.
 6 class Bildepanel extends JPanel
 7 {
 8   private ImageIcon ikon;
 9
10   public Bildepanel()
11   {
12     URL kilde = Bildepanel.class.getResource("bildefiler/Ipeint01.jpg");
13     if (kilde != null)
14     {
15       ikon = new ImageIcon(kilde);
16     }
17     else
18     {
19       ikon = null;
20     }
21   }
22
23   public void paintComponent(Graphics g)
24   {
25     super.paintComponent(g);
26     if (ikon != null)
27     {
28       ikon.paintIcon(this, g, 0, 0);
29     }
30     else
31     {
32       g.drawString("Fant ikke bildet!", 10, 50);
33     }
34   }
35
36   //Setter panelstørrelse lik bildets størrelse.
37   public Dimension getPreferredSize()
38   {
39     if (ikon != null)
40     {
41       return new Dimension(ikon.getIconWidth(), ikon.getIconHeight());
42     }
43     else
44     {
45       return new Dimension(200, 100);
46     }
47   }
48 }
49
50 class Bildevindu extends JFrame
51 {
52   public Bildevindu()
53   {
54     setTitle("Ikontest");
55     setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
56     Bildepanel bilde = new Bildepanel();
57     getContentPane().add(bilde, BorderLayout.CENTER);
58     pack();
59   }
60 }
61
62 public class Ikontest
63 {
64   public static void main(String[] args)
65   {
66     EventQueue.invokeLater(new Runnable()
67     {
68       public void run()
69       {
70         JFrame vindu = new Bildevindu();
71         vindu.setVisible(true);
72       }
73     });
74   }
75 }

Skalering

Skalering kan vi få til ved å hente ut ImageIcon-objektets Image-objekt ved instruksjonen

    Image imagebilde = bilde.getImage();

og tegne Image-bildet skalert, se nedenfor.

Tegne et Image-bilde

Image-bilder har ingen egen tegnemetode. Isteden må vi bruke Graphics-klassens drawImage-metode for å tegne dem. Denne finnes i flere versjoner. De to mest aktuelle å bruke er den for å tegne bildet i den størrelsen det har, og den for å tegne bildet i skalert størrelse. Bredde og høyde for det skalerte bildet angis da som ekstra parametre. Signaturene for disse to versjonene av tegnemetoden er som følger:

  boolean drawImage(Image bilde, int x, int y, ImageObserver sted)
  boolean drawImage(Image bilde, int x, int y, int bredde, int høyde,
                                                          ImageObserver sted)

Parametrene x og y angir i begge versjoner koordinatene for bildets øverste venstre hjørne relativt til tegneflata. Siste parameter skal vanligvis være en referanse til den komponent som bildet skal vises på. Som regel vil kallet på drawImage bli foretatt inne i paintComponent-metoden til et JPanel. Som siste parameter ved kallet bruker vi da kort og godt this.

Merknad De tekniske detaljene vedrørende uttegning av grafikk på skjermen er plattformavhengige. På grunn av dette er Graphics-klassen en abstrakt klasse som bare spesifiserer hvilke tegnemetoder vi skal ha tilgang til. Det Graphics-objektet som er aktuell parameter i et kall på paint eller paintComponent vil være av en eller annen subklassetype som er spesialtilpasset for det operativsystemet som programmet blir kjørt på. Det er da også et slikt objekt som blir brukt for å kalle opp de tegnemetodene som Graphics-klassen spesifiserer. Hvilken konkret subklassetype det er, trenger vi ikke å bry oss om. Det eneste vi trenger å vite er at det er en subtype til Graphics, slik at vi kan bruke det til å gjøre kall på de tegnemetodene som der er spesifisert.

Programeksempel 2

I vindusklassen Bildegalleri som er gjengitt nedenfor blir det opprettet 7 bilder i form av ImageIcon-objekter. De plasseres i hvert sitt panel. I panelet blir det også lagret data for bredde og høyde på tilgjengelig tegneflate på skjermen. Når bildene skal tegnes ut, blir det sjekket om bildehøyde er større enn høyden på tilgjengelig tegneflate. I så fall blir en skalert versjon av bildet tegnet ut. Skaleringen gjøres slik at forholdet mellom bredde og høyde i den skalerte versjonen blir som i originalen. Bildepanelene blir plassert som fanekort, der fanetittel er lik bildets tittel, mens kunstnerens navn vises som tooltiptekst. Driverklasse for programmet finnes i fila Bildevisning.java. Dersom innstillingene på din nettleser passer, kan du få kjørt en appletversjon av programmet i din nettleser ved å klikke på følgende link: Bildegalleri.

 1 import javax.swing.*;
 2 import java.awt.*;
 3 import java.net.*;
 4
 5 public class Bildegalleri extends JFrame
 6 {
 7   private int bredde, høyde;
 8   private int antPaneler = 8;
 9   private String[] bildefil =
10   {
11     "leonardo.jpg", "durer.jpg", "holbein.jpg",
12     "david.jpg", "rafael.jpg",
13     "rembrandt2461lr.jpg", "JulieManet.jpeg",
14     "Eugène_Delacroix_-_La_liberté_guidant_le_peuple.jpg"
15   };
16   private String[] kunstner =
17   {
18     "Leonardo da Vinci", "Albrecht Dürer",
19     "Hans Holbein", "Louis David", "Raphael",
20     "Rembrandt", "Pierre Auguste Renoir",
21     "Eugène Delacroix"
22   };
23   private String[] tittel =
24   {
25     "Mona Lisa", "Selvportrett", "Nikolas Kratzer",
26     "L'enlèvement des Sabines", "Den vakre gartnerinne",
27     "Selvportrett", "Julie Manet",
28     "La liberté guidant le peuple"
29   };
30
31   public Bildegalleri()
32   {
33     super("Bildegalleri");
34     JTabbedPane flikmappe = new JTabbedPane();
35     Bilde[] panel = new Bilde[antPaneler];
36     Dimension skjermstørrelse =
37             Toolkit.getDefaultToolkit().getScreenSize();
38     bredde = skjermstørrelse.width;
39     høyde = skjermstørrelse.height - 30; //trekker fra for oppgavelinje
40
41     for (int i = 0; i < panel.length; i++)
42     {
43       URL bildeURL = null;
44       bildeURL = Bildegalleri.class.getResource(
45               "bildefiler/" + bildefil[i]);
46       if (bildeURL != null)
47       {
48         panel[i] = new Bilde(new ImageIcon(bildeURL),
49                 bredde, høyde - 40); //trekker fra for flikhøyde
50         flikmappe.addTab(tittel[i], null, panel[i], kunstner[i]);
51       }
52       else
53       {
54         flikmappe.addTab("Tomt panel", new JPanel());
55       }
56     }
57
58     flikmappe.setSelectedIndex(0);
59     Container c = getContentPane();
60     c.add(flikmappe, BorderLayout.CENTER);
61     setSize(bredde, høyde);
62     setVisible(true);
63   }
64 }
65
66 class Bilde extends JPanel
67 {
68   private ImageIcon ikon;
69   private int bredde, høyde;
70
71   public Bilde(ImageIcon bilde, int b, int h)
72   {
73     bredde = b;
74     høyde = h;
75     ikon = bilde;
76   }
77
78   public void paintComponent(Graphics g)
79   {
80     super.paintComponent(g);
81     int bildebredde = ikon.getIconWidth();
82     int bildehøyde = ikon.getIconHeight();
83     if (bildehøyde > høyde)
84     { //skalerer om nødvendig:
85       bildebredde = bildebredde * høyde / bildehøyde;
86       bildehøyde = høyde;
87     }
88     if (bildebredde > bredde)
89     {
90       bildehøyde = bildehøyde * bredde / bildebredde;
91       bildebredde = bredde;
92     }
93     g.drawImage(ikon.getImage(), 0, 0, bildebredde, bildehøyde, this);
94   }
95 }

Opprette bilde i en applet

En applet kan bare laste inn bildedata fra serveren som appleten ligger på. Dette skyldes både sikkerhetshensyn og at det er mest logisk at appletens klassefiler og datafiler ligger på samme sted. For å laste inn bildedata fra en server, bruker en applet et URL-objekt på denne måte:

import java.net.*;
.
.
.
    URL kodekilde = getCodeBase(); // plassering av class-filene
    URL bildeURL = null;
    ImageIcon bilde = null;
    try
    {
      bildeURL = new URL( kodekilde, bildefilnavn );
      bilde = new ImageIcon( bildeURL );
    }
    catch ( MalformedURLException e )
    {
      ...
    }
.
.
.

Obs! Dersom vi bruker Eclipse, vil metoden getCodeBase returnere filstien for rotkatalogen til Eclipse-prosjektets class-filer. Når vi angir bildefilnavn, må vi derfor angi bildefilas relative plassering i forhold til denne katalogen.

Programeksempel 3

Appleten Bildegalleri4 er en appletversjon av samme programmet som er beskrevet i Programeksempel 2 like foran. Fullstendig kode for programmet (unntatt html-fil) er gjengitt nedenfor. Ved å klikke på linken Bildegalleri4.html, skal du kunne kjøre programmet i din nettleser.

 1 import javax.swing.*;
 2 import java.awt.*;
 3 import java.net.*;
 4
 5 public class Bildegalleri4 extends JApplet
 6 {
 7   private int bredde, høyde;
 8   private int antPaneler = 7;
 9   private String[] bildefil =
10   {
11     "leonardo.jpg", "durer.jpg", "holbein.jpg", "david.jpg",
12     "rafael.jpg", "rembrandt2461lr.jpg", "JulieManet.jpeg"
13   };
14   private String[] kunstner =
15   {
16     "Leonardo da Vinci", "Albrecht Dürer", "Hans Holbein",
17     "Louis David", "Raphael", "Rembrandt", "Pierre August Renoir"
18   };
19   private String[] tittel =
20   {
21     "Mona Lisa", "Selvportrett", "Nikolas Kratzer",
22     "L'enlèvement des Sabines", "Den vakre gartnerinne",
23     "Selvportrett", "Julie Manet"
24   };
25
26   public void init()
27   {
28     JTabbedPane flikmappe = new JTabbedPane();
29     Bildepanel2[] panel = new Bildepanel2[antPaneler];
30     Dimension skjermstørrelse =
31             Toolkit.getDefaultToolkit().getScreenSize();
32     bredde = skjermstørrelse.width;
33     høyde = skjermstørrelse.height - 100;
34     URL kodekilde = getCodeBase();
35
36     for (int i = 0; i < panel.length; i++)
37     {
38       URL bildeURL = null;
39       try
40       {
41         bildeURL = new URL(kodekilde,
42                 "bildefiler/" + bildefil[ i]);
43         panel[ i] = new Bildepanel2(new ImageIcon(bildeURL),
44                 bredde, høyde - 40);
45         flikmappe.addTab(tittel[i], null, panel[i], kunstner[i]);
46       }
47       catch (MalformedURLException e)
48       {
49         System.err.println("Fikk ikke opprettet URL for bildefil");
50       }
51     }
52
53     flikmappe.setSelectedIndex(0);
54     Container c = getContentPane();
55     c.add(flikmappe, BorderLayout.CENTER);
56     setSize(bredde, høyde);  //setter appletstørrelse
57   }
58 }
59
60 class Bildepanel2 extends JPanel
61 {
62   private ImageIcon ikon;
63   private int bredde, høyde;
64
65   public Bildepanel2(ImageIcon bilde, int b, int h)
66   {
67     bredde = b;
68     høyde = h;
69     ikon = bilde;
70   }
71
72   public void paintComponent(Graphics g)
73   {
74     super.paintComponent(g);
75     int bildebredde = ikon.getIconWidth();
76     int bildehøyde = ikon.getIconHeight();
77     if (bildehøyde > høyde)
78     {  //skalerer om nødvendig:
79       bildebredde = bildebredde * høyde / bildehøyde;
80       bildehøyde = høyde;
81     }
82     if (bildebredde > bredde)
83     {
84       bildehøyde = bildehøyde * bredde / bildebredde;
85       bildebredde = bredde;
86     }
87     g.drawImage(ikon.getImage(), 0, 0, bildebredde, bildehøyde, this);
88   }
89 }

Programeksempel 4

I programmet RepeterendeBilder som er gjengitt nedenfor, blir de tre bildene everest.gif, geit.gif og world.gif tegnet ut rekursivt i mindre og mindre format inntil en viss grense. Den opprinnelige kvadratiske tegneflata blir delt inn i fire like deler ved en horisontal og en vertikal delestrek. Den delen som ligger øverst til venstre holdes ledig, mens de tre andre blir brukt til å tegne de tre bildene med passende skalering. Deretter gjentar dette seg, men nå med den ledige delen som opprinnelig tegneflate. Rekursjonen stopper opp når sidekanten i ledig tegneflate er kommet under en bestemt grense. Resultatet blir et vindu lik det som er vist på bildet under.

  1 import java.awt.*;
  2 import javax.swing.*;
  3 import java.net.*;
  4
  5 // Demonsterer rekursiv tegnerutine.
  6 public class RepeterendeBilder extends JFrame
  7 {
  8   public RepeterendeBilder()
  9   {
 10     super("Repeterende bilder");
 11     getContentPane().add(new Bilderepetisjon(), BorderLayout.CENTER);
 12     pack();
 13     setVisible(true);
 14   }
 15
 16   public static void main(String[] args)
 17   {
 18     EventQueue.invokeLater(new Runnable()
 19     {
 20       public void run()
 21       {
 22         JFrame vindu = new RepeterendeBilder();
 23         vindu.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
 24       }
 25     });
 26   }
 27 }
 28
 29 class Bilderepetisjon extends JPanel
 30 {
 31   private final int BREDDE = 500;
 32   private final int HØYDE = 500;
 33   private final int MIN = 20;  // Minste bildestørrelse
 34   private Image jord, everest, geit;
 35   private ImageIcon jordikon, everestikon, geitikon;
 36
 37   public Bilderepetisjon()
 38   {
 39     URL bildeURL = null;
 40
 41     bildeURL = Bilderepetisjon.class.getResource(
 42             "bildefiler/world.gif");
 43     if (bildeURL != null)
 44     {
 45       jordikon = new ImageIcon(bildeURL);
 46     }
 47     else
 48     {
 49       JOptionPane.showMessageDialog(null,
 50               "Finner ikke bildefil world.gif.");
 51     }
 52     bildeURL = Bilderepetisjon.class.getResource(
 53             "bildefiler/everest.gif");
 54     if (bildeURL != null)
 55     {
 56       everestikon = new ImageIcon(bildeURL);
 57     }
 58     else
 59     {
 60       JOptionPane.showMessageDialog(null,
 61               "Finner ikke bildefil everest.gif.");
 62     }
 63     bildeURL = Bilderepetisjon.class.getResource(
 64             "bildefiler/geit.gif");
 65     if (bildeURL != null)
 66     {
 67       geitikon = new ImageIcon(bildeURL);
 68     }
 69     else
 70     {
 71       JOptionPane.showMessageDialog(null,
 72               "Finner ikke bildefil geit.gif.");
 73     }
 74
 75     jord = jordikon.getImage();
 76     everest = everestikon.getImage();
 77     geit = geitikon.getImage();
 78     setPreferredSize(new Dimension(BREDDE, HØYDE));
 79   }
 80
 81   //  Tegner det fullstendige skjermbildet rekursivt.
 82   public void tegnBilder(int side, Graphics g)
 83   {
 84     // Tegner tre bilder i hver sin firedel av tegneflata.
 85     g.drawImage(geit, 0, side / 2, side / 2, side / 2, this);
 86     g.drawImage(everest, side / 2, 0, side / 2, side / 2, this);
 87     g.drawImage(jord, side / 2, side / 2, side / 2, side / 2, this);
 88
 89     // Tegner de tre bildene om igjen med halvparten så lange 
 90     // sider i den ledige firedelen av vinduet, forutsatt at 
 91     // sidekanten er større enn den valgte minstestørrelsen.
 92     if (side > MIN)
 93     {
 94       tegnBilder(side / 2, g);
 95     }
 96   }
 97
 98   // Setter i gang rekursjonen ved å foreta startkallet på 
 99   // tegnBilder-metoden.
100   public void paintComponent(Graphics g)
101   {
102     super.paintComponent(g);
103     tegnBilder(BREDDE, g);
104   }
105 }

Hente inn bilder fra internett til en applikasjon

Applikasjoner kan hente inn bilder fra en hvilken som helst adresse på internett. Det skjer ved at det først opprettes et URL-objekt på grunnlag av adressen. Dette brukes som konstruktørparameter for ImageIcon-objektet.

Programeksempel 5

I programmet URLtest3.java som er gjengitt nedenfor blir det hentet inn et bilde fra Musée Marmottan i Paris (som blant annet har en omfattende samling bilder av Claude Monet), samt et bilde fra Wikipedia. Forutsetningen for at programmet skal virke er selvsagt at bildefilene befinner seg på de adressene som blir brukt, og at serverne som inneholder filene er i drift.

 1 import javax.swing.*;
 2 import java.awt.*;
 3 import java.net.*;
 4
 5 //Laster ned og viser bilder fra oppgitte nettadresser.
 6 class Kanvas extends JPanel
 7 {
 8   private ImageIcon bilde;
 9
10   public Kanvas(String bildeadresse)
11   {
12     try
13     {
14       bilde = new ImageIcon(new URL(bildeadresse));
15     }
16     catch (MalformedURLException e)
17     {
18       System.err.println("Ukjent protokoll.");
19     }
20   }
21
22   public void paintComponent(Graphics g)
23   {
24     super.paintComponent(g);
25     int bildebredde = bilde.getIconWidth();
26     int bildehøyde = bilde.getIconHeight();
27     Dimension skjermstørrelse =
28             Toolkit.getDefaultToolkit().getScreenSize();
29     int bredde = skjermstørrelse.width;
30     int høyde = skjermstørrelse.height - 100; //trekker fra for oppgavelinje
31     if (bildehøyde > høyde)
32     { //skalerer om nødvendig:
33       bildebredde = bildebredde * høyde / bildehøyde;
34       bildehøyde = høyde;
35     }
36     if (bildebredde > bredde)
37     {
38       bildehøyde = bildehøyde * bredde / bildebredde;
39       bildebredde = bredde;
40     }
41     g.drawImage(bilde.getImage(), 0, 0, bildebredde, bildehøyde, this);
42   }
43
44   public Dimension getPreferredSize()
45   {
46     return new Dimension(bilde.getIconWidth(), bilde.getIconHeight());
47   }
48 }
49
50 public class URLtest3 extends JFrame
51 {
52   public URLtest3()
53   {
54     super("Visning av nedlastede bilder.");
55     JTabbedPane bildemappe = new JTabbedPane();
56     Kanvas bilde1 = new Kanvas(
57             "http://www.marmottan.com/images/monet/Impress.jpg");
58     bildemappe.addTab("Claude Monet", null, bilde1,
59             "Impression Soleil Levant");
60     Kanvas bilde2 = new Kanvas(
61             "http://upload.wikimedia.org/wikipedia/commons/c/cb/Pierre-Auguste_Renoir_076.jpg");
62     bildemappe.addTab("Pierre Auguste Renoir", null,
63             bilde2, "Jeune fille au chapeau de paille");
64     getContentPane().add(bildemappe);
65   }
66
67   public static void main(String[] args)
68   {
69     EventQueue.invokeLater(new Runnable()
70     {
71       public void run()
72       {
73         URLtest3 test = new URLtest3();
74         test.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
75         test.pack();
76         test.setVisible(true);
77       }
78     });
79   }
80 }

Innholdsoversikt for programutvikling

Copyright © Kjetil Grønning, 2012