PelleP Skrevet 3. januar 2010 Del Skrevet 3. januar 2010 (endret) Jeg har eksperimentert litt med en bildeviser (se kode): import java.awt.*; import java.awt.event.*; import javax.swing.*; import javax.swing.SwingUtilities; import javax.swing.filechooser.*; import java.io.*; import java.io.File; import java.beans.*; public class Ikontest extends JFrame implements ActionListener{ public static void main(String[]args){ Ikontest ik = new Ikontest(); } ImageFilter imagefilter; JButton open; JButton avslutt; JPanel buttonpanel; JFileChooser filechooser; Bildepanel bildepanel; File file; Ikontest(){ bildepanel = new Bildepanel(); getContentPane().add(bildepanel, BorderLayout.CENTER); setTitle("Ikontest"); open = new JButton("Åpne bilde"); open.addActionListener(this); avslutt = new JButton("Avlsutt"); avslutt.addActionListener(this); buttonpanel = new JPanel(); buttonpanel.add(open); buttonpanel.add(avslutt); add(buttonpanel, BorderLayout.NORTH); setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE); pack(); setVisible(true); } public void actionPerformed(ActionEvent e){ if(e.getSource() == open){ filechooser = new JFileChooser(); filechooser.addChoosableFileFilter(new ImageFilter()); filechooser.setAcceptAllFileFilterUsed(false); filechooser.setFileView(new ImageFileView()); filechooser.setAccessory(new ImagePreview(filechooser)); int returnval = filechooser.showOpenDialog(this); if(returnval == JFileChooser.APPROVE_OPTION){ file = filechooser.getSelectedFile(); bildepanel.tegnBilde(file.getPath()); pack(); }else{ } }else{ System.exit(0); } } } class Bildepanel extends JPanel{ ImageIcon ikon; String path; Dimension resolution; int makswidth; int maksheight; int imagewidth; int imageheight; Bildepanel(){ resolution = Toolkit.getDefaultToolkit().getScreenSize(); makswidth = resolution.width; maksheight = resolution.height-40; } public void tegnBilde(String path){ this.path = path; ikon = new ImageIcon(path); } public void paintComponent(Graphics g){ imagewidth = ikon.getIconWidth(); imageheight = ikon.getIconHeight(); dimensionPrint(); if(imageheight > maksheight){ imagewidth = imagewidth * maksheight / imageheight; imageheight = maksheight; } if(imagewidth > makswidth){ imageheight = imageheight * makswidth / imagewidth; imagewidth = makswidth; dimensionPrint(); } getPreferredSize(); super.paintComponent(g); System.out.println("Tegner bilde"); g.drawImage(ikon.getImage(), 0, 0, imagewidth, imageheight, this); } public Dimension getPreferredSize(){ if(ikon != null){ return new Dimension(ikon.getIconWidth(), ikon.getIconHeight()); }else{ return new Dimension(600, 800); } } public void dimensionPrint(){//for test purpouse double format = ((double)(imagewidth/imageheight)); System.out.println(imagewidth); System.out.println(imageheight); System.out.println(format); } } class Utils{ public final static String jpeg = "jpeg"; public final static String jpg = "jpg"; public final static String gif = "gif"; public final static String tiff = "tiff"; public final static String tif = "tif"; public final static String png = "png"; /* * Get the extension of a file. */ public static String getExtension(File file) { String ext = null; String string = file.getName(); int i = string.lastIndexOf('.'); if (i > 0 && i < string.length() - 1){ ext = string.substring(i+1).toLowerCase(); } return ext; } protected static ImageIcon createImageIcon(String path) { java.net.URL imgURL = Utils.class.getResource(path); if (imgURL != null) { return new ImageIcon(imgURL); } else { System.err.println("Couldn't find file: " + path); return null; } } } class ImageFileView extends FileView { ImageIcon jpgIcon = Utils.createImageIcon("images/jpgIcon.gif"); ImageIcon gifIcon = Utils.createImageIcon("images/gifIcon.gif"); ImageIcon tiffIcon = Utils.createImageIcon("images/tiffIcon.gif"); ImageIcon pngIcon = Utils.createImageIcon("images/pngIcon.png"); public String getName(File f) { return null; //let the L&F FileView figure this out } public String getDescription(File f) { return null; //let the L&F FileView figure this out } public Boolean isTraversable(File f) { return null; //let the L&F FileView figure this out } public String getTypeDescription(File f) { String extension = Utils.getExtension(f); String type = null; if (extension != null) { if (extension.equals(Utils.jpeg) || extension.equals(Utils.jpg)) { type = "JPEG Image"; } else if (extension.equals(Utils.gif)){ type = "GIF Image"; } else if (extension.equals(Utils.tiff) || extension.equals(Utils.tif)) { type = "TIFF Image"; } else if (extension.equals(Utils.png)){ type = "PNG Image"; } } return type; } public Icon getIcon(File f) { String extension = Utils.getExtension(f); Icon icon = null; if (extension != null) { if (extension.equals(Utils.jpeg) || extension.equals(Utils.jpg)) { icon = jpgIcon; } else if (extension.equals(Utils.gif)) { icon = gifIcon; } else if (extension.equals(Utils.tiff) || extension.equals(Utils.tif)) { icon = tiffIcon; } else if (extension.equals(Utils.png)) { icon = pngIcon; } } return icon; } } class ImagePreview extends JComponent implements PropertyChangeListener { ImageIcon thumbnail = null; File file = null; public ImagePreview(JFileChooser fc) { setPreferredSize(new Dimension(100, 50)); fc.addPropertyChangeListener(this); } public void loadImage() { if (file == null) { thumbnail = null; return; } //Don't use createImageIcon (which is a wrapper for getResource) //because the image we're trying to load is probably not one //of this program's own resources. ImageIcon tmpIcon = new ImageIcon(file.getPath()); if (tmpIcon != null) { if (tmpIcon.getIconWidth() > 90) { thumbnail = new ImageIcon(tmpIcon.getImage(). getScaledInstance(90, -1, Image.SCALE_DEFAULT)); } else { //no need to miniaturize thumbnail = tmpIcon; } } } public void propertyChange(PropertyChangeEvent e) { boolean update = false; String prop = e.getPropertyName(); //If the directory changed, don't show an image. if (JFileChooser.DIRECTORY_CHANGED_PROPERTY.equals(prop)) { file = null; update = true; //If a file became selected, find out which one. } else if (JFileChooser.SELECTED_FILE_CHANGED_PROPERTY.equals(prop)) { file = (File) e.getNewValue(); update = true; } //Update the preview accordingly. if (update) { thumbnail = null; if (isShowing()) { loadImage(); repaint(); } } } protected void paintComponent(Graphics g) { if (thumbnail == null) { loadImage(); } if (thumbnail != null) { int x = getWidth()/2 - thumbnail.getIconWidth()/2; int y = getHeight()/2 - thumbnail.getIconHeight()/2; if (y < 0) { y = 0; } if (x < 5) { x = 5; } thumbnail.paintIcon(this, g, x, y); } } } Meningen er at man skal klikke på knappen "åpne bilde" få opp en JFileChooser som man velger et bilde med og bildet skal da tegnes på en JPanel. Deretter kan man velge et nytt bilde. På min 6-7 år gamle PC som jeg brukte til igår så fungerte det tilfredstillende. Når jeg flytter programmet over til min nye PC så tegnes bildet noen ganger men ikke andre. Jeg får følgende utskrift på skjermen: Tegner bilde Tegnet bilde 4000 3000 1.0 1280 960 1.0 Tegner bilde Tegnet bilde Exception in thread "Image Fetcher 0" java.lang.OutOfMemoryError: Java heap space at java.awt.image.DataBufferInt.<init>(DataBufferInt.java:41) at java.awt.image.Raster.createPackedRaster(Raster.java:458) at java.awt.image.DirectColorModel.createCompatibleWritableRaster(DirectColorModel.java:1015) at sun.awt.image.ImageRepresentation.createBufferedImage(ImageRepresentation.java:230) at sun.awt.image.ImageRepresentation.setPixels(ImageRepresentation.java:484) at sun.awt.image.ImageDecoder.setPixels(ImageDecoder.java:120) at sun.awt.image.JPEGImageDecoder.sendPixels(JPEGImageDecoder.java:97) at sun.awt.image.JPEGImageDecoder.readImage(Native Method) at sun.awt.image.JPEGImageDecoder.produceImage(JPEGImageDecoder.java:119) at sun.awt.image.InputStreamImageSource.doFetch(InputStreamImageSource.java:246) at sun.awt.image.ImageFetcher.fetchloop(ImageFetcher.java:172) at sun.awt.image.ImageFetcher.run(ImageFetcher.java:136) Exception in thread "Image Fetcher 0" java.lang.OutOfMemoryError: Java heap space at java.awt.image.DataBufferInt.<init>(DataBufferInt.java:41) at java.awt.image.Raster.createPackedRaster(Raster.java:458) at java.awt.image.DirectColorModel.createCompatibleWritableRaster(DirectColorModel.java:1015) at sun.awt.image.ImageRepresentation.createBufferedImage(ImageRepresentation.java:230) at sun.awt.image.ImageRepresentation.setPixels(ImageRepresentation.java:484) at sun.awt.image.ImageDecoder.setPixels(ImageDecoder.java:120) at sun.awt.image.JPEGImageDecoder.sendPixels(JPEGImageDecoder.java:97) at sun.awt.image.JPEGImageDecoder.readImage(Native Method) at sun.awt.image.JPEGImageDecoder.produceImage(JPEGImageDecoder.java:119) at sun.awt.image.InputStreamImageSource.doFetch(InputStreamImageSource.java:246) at sun.awt.image.ImageFetcher.fetchloop(ImageFetcher.java:172) at sun.awt.image.ImageFetcher.run(ImageFetcher.java:136) Exception in thread "Image Fetcher 0" java.lang.OutOfMemoryError: Java heap space 3000 4000 0.0 Tegner bilde Tegnet bilde 3000 4000 0.0 Tegner bilde Tegnet bilde 3000 4000 0.0 Tegner bilde Tegnet bilde 3000 4000 0.0 Tegner bilde Tegnet bilde Exception in thread "Image Fetcher 0" java.lang.OutOfMemoryError: Java heap space 4000 3000 1.0 1280 960 1.0 Tegner bilde Tegnet bilde 4000 3000 1.0 1280 960 1.0 Tegner bilde Tegnet bilde Nå lurer jeg veldig på to ting: Hvorfor blir paintComponent() kallet to ganger for hvert bilde jeg åpner? Hvorfor får jeg feilmeldingen: Exception in thread "Image Fetcher 0" java.lang.OutOfMemoryError: Java heap space Veldig takknemlig om noen kan hjelpe meg fordi jeg har stått fast med dette en stund nå... Endret 3. januar 2010 av PelleP Lenke til kommentar
PelleP Skrevet 3. januar 2010 Forfatter Del Skrevet 3. januar 2010 Jeg har nå funnet ut at dersom jeg kører programmet med følgende kommando: java -Xms32m -Xmx512m Ikontest så fungerer det. Det gjør vistnok at det allokeres mer minne til Java Virtual Machine enn det som er standard. Det virker ikke urimelig at programmet bruker mye minne. Speselt dersom det tegner hvert bilder to ganger! Lenke til kommentar
miXer Skrevet 5. januar 2010 Del Skrevet 5. januar 2010 For å sjekke minnebruk kan du sjekke Task manager(oppgavebehandling) Lenke til kommentar
PelleP Skrevet 5. januar 2010 Forfatter Del Skrevet 5. januar 2010 (endret) For å sjekke minnebruk kan du sjekke Task manager(oppgavebehandling) Det er vel kun i Windows? Derimot kan jeg bruke Java Monitoring and Management consol, det ser ut til at bruken av heapminne ligger på maks 45 mb og feilmeldingen synes ikke nødvendigvis å komme i forbindelse med noen topp i minnebruken. Snodig! Uansett så er jeg mest interessert i å vite hvorfor paintComponent() blir utført to ganger for hver gang et bilde blir lastet. Det er sikkert en elementær grunnelggende feil i mitt program, det må da være noen som kan forklare?? Endret 5. januar 2010 av PelleP Lenke til kommentar
Unlimited LTD Skrevet 6. januar 2010 Del Skrevet 6. januar 2010 paintComponent blir kalt hver gang bildet tegnes på nytt. Det er nok mest sannsynlig bare fordi det trenger det? Når jeg kjørte koden tegnet den bare en gang. Men har det noe å si for programmet ditt? Lenke til kommentar
PelleP Skrevet 6. januar 2010 Forfatter Del Skrevet 6. januar 2010 paintComponent blir kalt hver gang bildet tegnes på nytt. Det er nok mest sannsynlig bare fordi det trenger det? Når jeg kjørte koden tegnet den bare en gang. Men har det noe å si for programmet ditt? Nei, forsåvidt så fungerer det jo, men minnebruken blir vel høyere? Det må finnes en bedre måte å gjøre det på? Lenke til kommentar
Unlimited LTD Skrevet 6. januar 2010 Del Skrevet 6. januar 2010 Den bare tegner til det samme bildet, altså endrer piksler i et allerede eksisterende bilde, med mindre du har egen kode der. Ikke spesielt krevende. Lenke til kommentar
PelleP Skrevet 6. januar 2010 Forfatter Del Skrevet 6. januar 2010 Den bare tegner til det samme bildet, altså endrer piksler i et allerede eksisterende bilde, med mindre du har egen kode der. Ikke spesielt krevende. Det ser ut til at paintComponent() blir kallet hver gang man åpner et nytt bilde. Man kan ikke se det, men ettersom programmet skriver til skjermen hver gange metoden blir kallet så kan jeg se det på skjermutskriften. Jeg prøvde å endre koden slik: public void actionPerformed(ActionEvent e){ if(e.getSource() == open){ filechooser = new JFileChooser(defaultpath); ... int returnval = filechooser.showOpenDialog(this); if(returnval == JFileChooser.APPROVE_OPTION){ if(bildepanel != null){ remove(bildepanel); } file = filechooser.getSelectedFile(); bildepanel = new Bildepanel(file.getPath()); getContentPane().add(bildepanel, BorderLayout.CENTER); defaultpath = file.getPath(); pack(); Problemet mitt var at jeg måtte fjerne bildet som allerede var tegnet før jeg kunne tegne et nytt, ellers så blir de tegnet oppå hverandre. Når jeg bruker remove(bildepanel) for å fjerne bildepanelet før jeg konstruerer det på nytt, så slipper jeg å kalle på paintcomponent() og da blir bildet ikke tegnet dobbelt opp. Jeg får imidlirtid fortsatt java.lang.OutOfMemoryError hvis jeg ikke kjører programmet med ekstra heap-minne. Slik sett så kan det jo være det samme! Lenke til kommentar
Myrsnipe Skrevet 8. januar 2010 Del Skrevet 8. januar 2010 Anbefaller deg å profilere programmet for å se hva som bruker så mye minne Lenke til kommentar
PelleP Skrevet 8. januar 2010 Forfatter Del Skrevet 8. januar 2010 (endret) Anbefaller deg å profilere programmet for å se hva som bruker så mye minne Sikkert en god idé! Hva er det og hvordan gjør jeg det? Kan jeg bruke Virtual Java Console til det? Jeg har videreutviklet programmet slik at den viser flere bilder etter hverandre: import java.awt.*; import java.awt.event.*; import javax.swing.*; import javax.swing.SwingUtilities; import javax.swing.filechooser.*; import java.io.*; import java.io.File; import java.beans.*; import java.util.*; public class Ikontest extends JFrame implements ActionListener, Runnable{ public static void main(String[]args){ Ikontest ik = new Ikontest(); } Bildepanel bildepanel; Forsinkelse forsinkelse; ImageFilter imagefilter; JButton open; JButton avslutt; JLabel information; JPanel informationpanel; JPanel buttonpanel; JFileChooser filechooser; File files[]; String defaultpath = "/home/pelle/Bilder/"; Ikontest(){ forsinkelse = new Forsinkelse(); setTitle("Ikontest"); open = new JButton("Åpne bilde"); open.addActionListener(this); avslutt = new JButton("Avlsutt"); avslutt.addActionListener(this); information = new JLabel(" "); informationpanel = new JPanel(); informationpanel.setBackground(Color.white); information.setForeground(Color.blue); informationpanel.add(information); buttonpanel = new JPanel(); buttonpanel.add(open); buttonpanel.add(avslutt); buttonpanel.add(informationpanel); add(buttonpanel, BorderLayout.NORTH); setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE); pack(); setVisible(true); } public void actionPerformed(ActionEvent e){ if(e.getSource() == open){ filechooser = new JFileChooser(defaultpath); filechooser.setMultiSelectionEnabled(true); filechooser.addChoosableFileFilter(new ImageFilter()); filechooser.setAcceptAllFileFilterUsed(false); filechooser.setFileView(new ImageFileView()); filechooser.setAccessory(new ImagePreview(filechooser)); int returnval = filechooser.showOpenDialog(this); if(returnval == JFileChooser.APPROVE_OPTION){ if(bildepanel != null){ remove(bildepanel); } files = filechooser.getSelectedFiles(); new Thread(this).start(); }else{ } }else{ System.exit(0); } } public void fremvising(){ for(int i = 0;i < files.length;i++){ information.setText(files[i].getName()); bildepanel = new Bildepanel(files[i].getPath()); getContentPane().add(bildepanel, BorderLayout.CENTER); pack(); forsinkelse.forsinke(4000); remove(bildepanel); } information.setText("Lysbildevisning slutt"); } public void run() { fremvising(); } } class Bildepanel extends JPanel{ Forsinkelse forsinkelse; ImageIcon ikon; String path; Dimension resolution; int makswidth; int maksheight; int imagewidth; int imageheight; int indent; Bildepanel(String path){ System.out.println("Ny bildepanel: " + path ); setBackground(Color.black); ikon = new ImageIcon(path); forsinkelse = new Forsinkelse(); resolution = Toolkit.getDefaultToolkit().getScreenSize(); makswidth = resolution.width; maksheight = resolution.height-40; imagewidth = ikon.getIconWidth(); imageheight = ikon.getIconHeight(); dimensionPrint(); if(imageheight > maksheight){ imagewidth = imagewidth * maksheight / imageheight; imageheight = maksheight; } if(imagewidth > makswidth){ imageheight = imageheight * makswidth / imagewidth; imagewidth = makswidth; } indent = (resolution.width-imagewidth)/2; dimensionPrint(); getPreferredSize(); } public void paintComponent(Graphics g){ if(ikon != null){ System.out.println(imagewidth + ":" + imageheight); super.paintComponent(g); System.out.println("Tegner bilde"); g.drawImage(ikon.getImage(), indent, 0, imagewidth, imageheight, this); //forsinkelse.forsinke(5000); } } public Dimension getPreferredSize(){ System.out.println("getPrefferedSize()"); if(ikon != null){ return new Dimension(resolution.width, resolution.height-40); }else{ return new Dimension(600, 800); } } public void dimensionPrint(){//for test purpouse double format = ((double)(imagewidth/imageheight)); System.out.println(imagewidth); System.out.println(imageheight); System.out.println(format); } } class Utils{ public final static String jpeg = "jpeg"; public final static String jpg = "jpg"; public final static String gif = "gif"; public final static String tiff = "tiff"; public final static String tif = "tif"; public final static String png = "png"; /* * Get the extension of a file. */ public static String getExtension(File file) { String ext = null; String string = file.getName(); int i = string.lastIndexOf('.'); if (i > 0 && i < string.length() - 1){ ext = string.substring(i+1).toLowerCase(); } return ext; } protected static ImageIcon createImageIcon(String path) { java.net.URL imgURL = Utils.class.getResource(path); if (imgURL != null) { return new ImageIcon(imgURL); } else { System.err.println("Couldn't find file: " + path); return null; } } } class ImageFileView extends FileView { ImageIcon jpgIcon = Utils.createImageIcon("images/jpgIcon.gif"); ImageIcon gifIcon = Utils.createImageIcon("images/gifIcon.gif"); ImageIcon tiffIcon = Utils.createImageIcon("images/tiffIcon.gif"); ImageIcon pngIcon = Utils.createImageIcon("images/pngIcon.png"); public String getName(File f) { return null; //let the L&F FileView figure this out } public String getDescription(File f) { return null; //let the L&F FileView figure this out } public Boolean isTraversable(File f) { return null; //let the L&F FileView figure this out } public String getTypeDescription(File f) { String extension = Utils.getExtension(f); String type = null; if (extension != null) { if (extension.equals(Utils.jpeg) || extension.equals(Utils.jpg)) { type = "JPEG Image"; } else if (extension.equals(Utils.gif)){ type = "GIF Image"; } else if (extension.equals(Utils.tiff) || extension.equals(Utils.tif)) { type = "TIFF Image"; } else if (extension.equals(Utils.png)){ type = "PNG Image"; } } return type; } public Icon getIcon(File f) { String extension = Utils.getExtension(f); Icon icon = null; if (extension != null) { if (extension.equals(Utils.jpeg) || extension.equals(Utils.jpg)) { icon = jpgIcon; } else if (extension.equals(Utils.gif)) { icon = gifIcon; } else if (extension.equals(Utils.tiff) || extension.equals(Utils.tif)) { icon = tiffIcon; } else if (extension.equals(Utils.png)) { icon = pngIcon; } } return icon; } } class ImagePreview extends JComponent implements PropertyChangeListener { ImageIcon thumbnail = null; File file = null; public ImagePreview(JFileChooser fc) { setPreferredSize(new Dimension(100, 50)); fc.addPropertyChangeListener(this); } public void loadImage() { if (file == null) { thumbnail = null; return; } //Don't use createImageIcon (which is a wrapper for getResource) //because the image we're trying to load is probably not one //of this program's own resources. ImageIcon tmpIcon = new ImageIcon(file.getPath()); if (tmpIcon != null) { if (tmpIcon.getIconWidth() > 90) { thumbnail = new ImageIcon(tmpIcon.getImage(). getScaledInstance(90, -1, Image.SCALE_DEFAULT)); } else { //no need to miniaturize thumbnail = tmpIcon; } } } public void propertyChange(PropertyChangeEvent e) { boolean update = false; String prop = e.getPropertyName(); //If the directory changed, don't show an image. if (JFileChooser.DIRECTORY_CHANGED_PROPERTY.equals(prop)) { file = null; update = true; //If a file became selected, find out which one. } else if (JFileChooser.SELECTED_FILE_CHANGED_PROPERTY.equals(prop)) { file = (File) e.getNewValue(); update = true; } //Update the preview accordingly. if (update) { thumbnail = null; if (isShowing()) { loadImage(); repaint(); } } } protected void paintComponent(Graphics g) { if (thumbnail == null) { loadImage(); } if (thumbnail != null) { int x = getWidth()/2 - thumbnail.getIconWidth()/2; int y = getHeight()/2 - thumbnail.getIconHeight()/2; if (y < 0) { y = 0; } if (x < 5) { x = 5; } thumbnail.paintIcon(this, g, x, y); } } } ImageFileter.java: import java.io.File; import javax.swing.*; import javax.swing.filechooser.*; /* ImageFilter.java is used by FileChooserDemo2.java. */ public class ImageFilter extends FileFilter { //Accept all directories and all gif, jpg, tiff, or png files. public boolean accept(File f) { if (f.isDirectory()) { return true; } String extension = Utils.getExtension(f); if (extension != null) { if (extension.equals(Utils.tiff) || extension.equals(Utils.tif) || extension.equals(Utils.gif) || extension.equals(Utils.jpeg) || extension.equals(Utils.jpg) || extension.equals(Utils.png)) { return true; } else { return false; } } return false; } //The description of this filter public String getDescription() { return "Just Images"; } } Forsinkelse.java class Forsinkelse extends Thread{ boolean stop = false; int msekunder; Forsinkelse(){ msekunder = 500; } Forsinkelse(int msekunder){ this.msekunder = msekunder; } public void forsinke(){ try{ Thread.sleep(1 * msekunder); } catch (InterruptedException e){} System.out.println("msekunder = " + msekunder); } public void forsinke(int msekunder){ this.msekunder = msekunder; try{ Thread.sleep(1 * msekunder); } catch (InterruptedException e){} System.out.println("msekunder = " + msekunder); } } Igjen så er det et ubegripelig "dobbeltkall" på PaintComponent(), og den her gangen skjønner jeg virkelig ikke hvorfor. Maks minneforbruk under siste kjøring: 600 Mb Endret 8. januar 2010 av PelleP Lenke til kommentar
Unlimited LTD Skrevet 8. januar 2010 Del Skrevet 8. januar 2010 Prøv å pakke inn koden din i tags. Gjør det enklere å se gjennom. Lenke til kommentar
PelleP Skrevet 8. januar 2010 Forfatter Del Skrevet 8. januar 2010 Prøv å pakke inn koden din i tags. Gjør det enklere å se gjennom.OK, det er gjort! Lenke til kommentar
Anbefalte innlegg
Opprett en konto eller logg inn for å kommentere
Du må være et medlem for å kunne skrive en kommentar
Opprett konto
Det er enkelt å melde seg inn for å starte en ny konto!
Start en kontoLogg inn
Har du allerede en konto? Logg inn her.
Logg inn nå