java ग्राफिक्स को संग्रहीत करना एक अच्छा विचार है?




सुंदर विचार फोटो (4)

नहीं, Graphics ऑब्जेक्ट को संग्रहीत करना आमतौर पर एक बुरा विचार है। :-)

यहां बताया गया है: आम तौर पर, Graphics उदाहरण अल्पकालिक होते हैं और किसी प्रकार की सतह पर पेंट या ड्रॉ करने के लिए उपयोग किया जाता है (आमतौर पर एक (J)Component या BufferedImage )। इसमें रंग, स्ट्रोक, स्केल, रोटेशन इत्यादि जैसे इन ड्राइंग ऑपरेशंस की स्थिति है। हालांकि, यह ड्राइंग ऑपरेशंस या पिक्सल का नतीजा नहीं रखता है।

इस वजह से, यह पूर्व-कार्यक्षमता प्राप्त करने में आपकी सहायता नहीं करेगा। पिक्सेल घटक या छवि से संबंधित है। तो, एक "पिछली" Graphics ऑब्जेक्ट पर वापस रोलिंग पिक्सल को पिछले स्थिति में संशोधित नहीं करेगा।

यहां कुछ दृष्टिकोण हैं जिन्हें मैं जानता हूं:

  • छवि को संशोधित करने के लिए कमांड (कमांड पैटर्न) की "श्रृंखला" का प्रयोग करें। कमांड पैटर्न पूर्ववत / फिर से (और स्विंग / एडब्ल्यूटी में Action में लागू किया गया है) के साथ बहुत अच्छा काम करता है। मूल से शुरू, अनुक्रम में सभी आदेश प्रस्तुत करें। प्रो: प्रत्येक कमांड में राज्य आमतौर पर इतना बड़ा नहीं होता है, जिससे आपको स्मृति में पूर्व-बफर के कई चरण मिलते हैं। Con: बहुत सारे परिचालन के बाद, यह धीमा हो जाता है ...

  • प्रत्येक ऑपरेशन के लिए, पूरे BufferedImage स्टोर करें (जैसा कि आपने मूल रूप से किया था)। प्रो: लागू करने में आसान है। Con: आप स्मृति से तेजी से भाग लेंगे। युक्ति: आप अधिक प्रसंस्करण समय की लागत पर, कम स्मृति लेना पूर्ववत / फिर से बनाने, छवियों को क्रमबद्ध कर सकते हैं।

  • कमांड पैटर्न / चेन विचार का उपयोग करते हुए उपर्युक्त का संयोजन, लेकिन उचित होने पर "स्नैपशॉट्स" ( BufferedImages रूप में) के साथ प्रतिपादन को अनुकूलित करना। मतलब है कि आपको प्रत्येक नए ऑपरेशन (तेज़) के लिए शुरुआत से सबकुछ प्रस्तुत करने की आवश्यकता नहीं होगी। मेमोरी से बाहर निकलने से बचने के लिए डिस्क पर इन स्नैपशॉट को फ्लश / क्रमबद्ध करें (लेकिन यदि आप कर सकते हैं, तो स्मृति में उन्हें रखें)। वर्चुअल असीमित पूर्ववत के लिए, आप कमांड को डिस्क पर क्रमबद्ध भी कर सकते हैं। प्रो: सही होने पर महान काम करता है। Con: सही पाने के लिए कुछ समय लगेगा।

पीएस: उपर्युक्त सभी के लिए, आपको एक उत्तरदायी यूआई रखने के लिए, पृष्ठभूमि में प्रदर्शित छवि को अद्यतन करने के लिए, पृष्ठभूमि में डिस्क आदि को संग्रहीत करने के लिए पृष्ठभूमि थ्रेड (जैसे SwingWorker या इसी तरह) का उपयोग करने की आवश्यकता है।

सौभाग्य! :-)

मैं वर्तमान में जावा में एक पेंट प्रोग्राम लिखने की प्रक्रिया में हूं, जो लचीली और व्यापक कार्यक्षमताओं के लिए डिज़ाइन किया गया है। यह मेरी अंतिम परियोजना से निकला, कि मैंने दिन पहले रात भर लिखा था। इसके कारण, इसमें टन और टन कीड़े हैं, जिन्हें मैं एक-एक करके सामना कर रहा हूं (उदाहरण के लिए मैं केवल उन फ़ाइलों को सहेज सकता हूं जो खाली होंगी, मेरे आयताकार सही नहीं होते हैं लेकिन मेरी मंडलियां ...)।

इस बार, मैं अपने कार्यक्रम में पूर्ववत / फिर से कार्यक्षमता जोड़ने की कोशिश कर रहा हूं। हालांकि, मैं कुछ ऐसा नहीं कर सकता जो मैंने किया है। इसलिए, मुझे हर बार mouseReleased ईवेंट को निकाल दिया गया था, जब भी मेरे BufferedImage की प्रतियों को बचाने के लिए एक विचार मिला। हालांकि, 1920x1080 रिज़ॉल्यूशन पर जाने वाली कुछ छवियों के साथ, मुझे लगा कि यह कुशल नहीं होगा: उन्हें संग्रहीत करने से शायद स्मृति की गीगाबाइट्स ले जाएंगी।

कारण मैं पूर्ववत करने के लिए पृष्ठभूमि रंग के साथ एक ही चीज़ को पेंट नहीं कर सकता हूं क्योंकि मेरे पास कई अलग-अलग ब्रश हैं, जो Math.random() पर आधारित पेंट हैं, और क्योंकि कई अलग-अलग परतें हैं (एक परत में) ।

फिर, मैंने Graphics ऑब्जेक्ट्स क्लोनिंग पर विचार किया है जिसे मैं BufferedImage पेंट करने के लिए उपयोग करता हूं। इस कदर:

ArrayList<Graphics> revisions = new ArrayList<Graphics>();

@Override
public void mouseReleased(MouseEvent event) {
    Graphics g = image.createGraphics();
    revisions.add(g);
}

मैंने पहले ऐसा नहीं किया है, इसलिए मेरे पास कुछ प्रश्न हैं:

  • क्या मैं अभी भी यह कर कर व्यर्थ स्मृति को बर्बाद कर दूंगा, जैसे कि मेरे BufferedImages क्लोनिंग?
  • क्या यह एक अलग तरीका है जो मैं कर सकता हूं?

अधिकांश गेम (या प्रोग्राम) केवल आवश्यक भागों को बचाता है और आपको यह करना चाहिए कि आपको क्या करना चाहिए।

  • एक आयताकार चौड़ाई, ऊंचाई, पृष्ठभूमि रंग, स्ट्रोक, रूपरेखा इत्यादि द्वारा प्रदर्शित किया जा सकता है। तो आप वास्तविक आयत के बजाय इन पैरामीटर को केवल सहेज सकते हैं। "आयताकार रंग: लाल चौड़ाई: 100 ऊंचाई 100"

  • आपके प्रोग्राम के यादृच्छिक पहलुओं (ब्रश पर यादृच्छिक रंग) के लिए आप या तो बीज को बचा सकते हैं या परिणाम बचा सकते हैं। "यादृच्छिक बीज: 1023920"

  • यदि कार्यक्रम छवियों को आयात करने की अनुमति देता है तो आपको छवियों की प्रतिलिपि बनाना और सहेजना चाहिए।

  • fillters और प्रभाव (ज़ूम / परिवर्तन / चमक) सभी आकारों की तरह पैरामीटर द्वारा प्रतिनिधित्व किया जा सकता है। जैसे। "ज़ूम स्केल: 2" "घूर्णन कोण: 30"

  • इसलिए आप इन सभी पैरामीटर को एक सूची में सहेजते हैं और फिर जब आपको पूर्ववत करने की आवश्यकता होती है तो आप पैरामीटर को हटाए जाने के रूप में चिह्नित कर सकते हैं (लेकिन वास्तव में उन्हें हटाएं क्योंकि आप भी फिर से करने में सक्षम होना चाहते हैं)। फिर आप पूरे कैनवास को मिटा सकते हैं और पैरामीटर के आधार पर छवि को फिर से बना सकते हैं जिन्हें हटाए गए चिह्नित किया गया था।

* लाइनों जैसी चीजों के लिए आप बस अपने स्थानों को एक सूची में स्टोर कर सकते हैं।


आप अपनी छवियों को संपीड़ित करने की कोशिश करना चाहते हैं (पीएनजी का उपयोग करना एक अच्छी शुरुआत है, इसमें कुछ अच्छे फिल्टर हैं जो ज़्लिब संपीड़न के साथ वास्तव में मदद करते हैं)। मुझे लगता है कि ऐसा करने का सबसे अच्छा तरीका है

  • इसे संशोधित करने से पहले छवि की एक प्रति बनाएं
  • इसे संशोधित करें
  • नई संशोधित छवि के साथ प्रतिलिपि की तुलना करें
  • प्रत्येक पिक्सेल के लिए आपने नहीं बदला है, उस पिक्सेल को एक काला, पारदर्शी पिक्सेल बनाएं।

वास्तव में पीएनजी में वास्तव में अच्छी तरह से संपीड़ित होना चाहिए। काले और सफेद आज़माएं और देखें कि क्या कोई अंतर है (मुझे नहीं लगता कि वहां होगा, लेकिन सुनिश्चित करें कि आप आरजीबी मानों को एक ही चीज़ पर सेट करते हैं, न केवल अल्फा मान, इसलिए यह बेहतर संपीड़ित होगा)।

आपको उस हिस्से में छवि को फसल करने से भी बेहतर प्रदर्शन मिल सकता है, लेकिन मुझे यकीन नहीं है कि संपीड़न पर विचार करने से आपको कितना लाभ होता है (और तथ्य यह है कि आपको अब ऑफसेट को सहेजना और याद रखना होगा) ।

फिर, चूंकि आपके पास अल्फा चैनल है, यदि वे पूर्ववत करते हैं, तो आप वर्तमान छवि के शीर्ष पर पूर्ववत छवि को वापस रख सकते हैं और आप सेट हैं।


आइडिया # 1, Graphics ऑब्जेक्ट्स को संग्रहीत करना बस काम नहीं करेगा। Graphics को कुछ डिस्प्ले मेमोरी "होल्डिंग" के रूप में नहीं माना जाना चाहिए, बल्कि डिस्प्ले मेमोरी के क्षेत्र तक पहुंचने के लिए हैंडल के रूप में नहीं माना जाना चाहिए। BufferedImage के मामले में, प्रत्येक Graphics ऑब्जेक्ट हमेशा एक ही छवि मेमोरी बफर के लिए हैंडल होगा, इसलिए वे सभी एक ही छवि का प्रतिनिधित्व करेंगे। इससे भी महत्वपूर्ण बात यह है कि आप वास्तव में संग्रहीत Graphics साथ कुछ भी नहीं कर सकते हैं: क्योंकि वे कुछ भी स्टोर नहीं करते हैं, वैसे भी कोई भी तरीका नहीं है कि वे कुछ भी "पुनः स्टोर" कर सकें।

आइडिया # 2, बुफर्ड BufferedImage एस क्लोनिंग एक बेहतर विचार है, लेकिन आप वास्तव में स्मृति बर्बाद कर देंगे, और जल्दी से इसे खत्म कर देंगे। यह केवल ड्रॉ से प्रभावित छवि के उन हिस्सों को स्टोर करने में मदद करता है, उदाहरण के लिए आयताकार क्षेत्रों का उपयोग करना, लेकिन यह अभी भी बहुत सारी मेमोरी खर्च करता है। डिस्क पर उन पूर्ववत छवियों को बफर करने से मदद मिल सकती है, लेकिन यह आपके यूआई को धीमा और उत्तरदायी बना देगा, और यह बुरा है ; इसके अलावा, यह आपको एप्लिकेशन को अधिक जटिल और त्रुटि-प्रवण बनाता है।

मेरा विकल्प छवि संशोधनों को एक सूची में स्टोर करना होगा, जो छवि के शीर्ष पर पहले से अंतिम तक प्रस्तुत किया जाएगा। एक पूर्ववत ऑपरेशन में बस सूची से संशोधन को हटाने का होता है।

इसके लिए आपको छवि संशोधनों को "सुधारने" की आवश्यकता होती है, यानी एक ऐसी कक्षा बनाएं जो एक एकल void draw(Graphics gfx) विधि प्रदान करके एक एकल संशोधन लागू करे जो वास्तविक ड्राइंग करता है।

जैसा कि आपने कहा था, यादृच्छिक संशोधन एक अतिरिक्त समस्या उत्पन्न करते हैं। हालांकि, यादृच्छिक संख्या बनाने के लिए मुख्य समस्या Math.random() का उपयोग है। इसके बजाए, एक निश्चित बीज मान से बनाए गए यादृच्छिक संशोधन के साथ प्रत्येक यादृच्छिक संशोधन करें, ताकि (छद्म-) यादृच्छिक संख्या अनुक्रम draw() प्रत्येक आमंत्रण पर समान हों, यानी, प्रत्येक ड्रॉ का बिल्कुल वही प्रभाव होता है। (यही कारण है कि उन्हें "छद्म-यादृच्छिक" कहा जाता है - जेनरेट की गई संख्या यादृच्छिक लगती है, लेकिन वे किसी भी अन्य कार्य के रूप में निर्धारक हैं।)

छवि भंडारण तकनीक के विपरीत, जिसमें स्मृति समस्याएं हैं, इस तकनीक के साथ समस्या यह है कि कई संशोधनों में जीयूआई धीमा हो सकता है, खासकर यदि संशोधन कम्प्यूटेशनल गहन हैं। इसे रोकने के लिए, सबसे आसान तरीका पूर्ववत संशोधनों की सूची के उचित अधिकतम आकार को ठीक करना होगा। यदि यह सीमा एक नया संशोधन जोड़कर पार हो जाएगी, तो सूची में सबसे पुराना संशोधन हटा दें और बैकिंग BufferedImage पर इसे लागू करें।

निम्नलिखित सरल डेमो एप्लिकेशन से पता चलता है कि (और कैसे) यह सब एक साथ काम करता है। इसमें पूर्ववत कार्रवाइयों को फिर से करने के लिए एक अच्छी "फिर से" सुविधा भी शामिल है।

package ;

import java.awt.*;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.awt.image.BufferedImage;
import java.util.LinkedList;
import java.util.Random;
import javax.swing.*;

public final class UndoableDrawDemo
        implements Runnable
{
    public static void main(String[] args) {
        EventQueue.invokeLater(new UndoableDrawDemo()); // execute on EDT
    }

    // holds the list of drawn modifications, rendered back to front
    private final LinkedList<ImageModification> undoable = new LinkedList<>();
    // holds the list of undone modifications for redo, last undone at end
    private final LinkedList<ImageModification> undone = new LinkedList<>();

    // maximum # of undoable modifications
    private static final int MAX_UNDO_COUNT = 4;

    private BufferedImage image;

    public UndoableDrawDemo() {
        image = new BufferedImage(600, 600, BufferedImage.TYPE_INT_RGB);
    }

    public void run() {
        // create display area
        final JPanel drawPanel = new JPanel() {
            @Override
            public void paintComponent(Graphics gfx) {
                super.paintComponent(gfx);

                // display backing image
                gfx.drawImage(image, 0, 0, null);

                // and render all undoable modification
                for (ImageModification action: undoable) {
                    action.draw(gfx, image.getWidth(), image.getHeight());
                }
            }

            @Override
            public Dimension getPreferredSize() {
                return new Dimension(image.getWidth(), image.getHeight());
            }
        };

        // create buttons for drawing new stuff, undoing and redoing it
        JButton drawButton = new JButton("Draw");
        JButton undoButton = new JButton("Undo");
        JButton redoButton = new JButton("Redo");

        drawButton.addActionListener(new ActionListener() {
            public void actionPerformed(ActionEvent e) {
                // maximum number of undo's reached?
                if (undoable.size() == MAX_UNDO_COUNT) {
                    // remove oldest undoable action and apply it to backing image
                    ImageModification first = undoable.removeFirst();

                    Graphics imageGfx = image.getGraphics();
                    first.draw(imageGfx, image.getWidth(), image.getHeight());
                    imageGfx.dispose();
                }

                // add new modification
                undoable.addLast(new ExampleRandomModification());

                // we shouldn't "redo" the undone actions
                undone.clear();

                drawPanel.repaint();
            }
        });

        undoButton.addActionListener(new ActionListener() {
            public void actionPerformed(ActionEvent e) {
                if (!undoable.isEmpty()) {
                    // remove last drawn modification, and append it to undone list
                    ImageModification lastDrawn = undoable.removeLast();
                    undone.addLast(lastDrawn);

                    drawPanel.repaint();
                }
            }
        });

        redoButton.addActionListener(new ActionListener() {
            public void actionPerformed(ActionEvent e) {
                if (!undone.isEmpty()) {
                    // remove last undone modification, and append it to drawn list again
                    ImageModification lastUndone = undone.removeLast();
                    undoable.addLast(lastUndone);

                    drawPanel.repaint();
                }
            }
        });

        JPanel buttonPanel = new JPanel(new FlowLayout());
        buttonPanel.add(drawButton);
        buttonPanel.add(undoButton);
        buttonPanel.add(redoButton);

        // create frame, add all content, and open it
        JFrame frame = new JFrame("Undoable Draw Demo");
        frame.getContentPane().add(drawPanel);
        frame.getContentPane().add(buttonPanel, BorderLayout.NORTH);
        frame.pack();
        frame.setDefaultCloseOperation(JFrame.DISPOSE_ON_CLOSE);
        frame.setLocationRelativeTo(null);
        frame.setVisible(true);
    }

    //--- draw actions ---

    // provides the seeds for the random modifications -- not for drawing itself
    private static final Random SEEDS = new Random();

    // interface for draw modifications
    private interface ImageModification
    {
        void draw(Graphics gfx, int width, int height);
    }

    // example random modification, draws bunch of random lines in random color
    private static class ExampleRandomModification implements ImageModification
    {
        private final long seed;

        public ExampleRandomModification() {
            // create some random seed for this modification
            this.seed = SEEDS.nextLong();
        }

        @Override
        public void draw(Graphics gfx, int width, int height) {
            // create a new pseudo-random number generator with our seed...
            Random random = new Random(seed);

            // so that the random numbers generated are the same each time.
            gfx.setColor(new Color(
                    random.nextInt(256), random.nextInt(256), random.nextInt(256)));

            for (int i = 0; i < 16; i++) {
                gfx.drawLine(
                        random.nextInt(width), random.nextInt(height),
                        random.nextInt(width), random.nextInt(height));
            }
        }
    }
}






bufferedimage