কখনও কখনও আমার একটি লসলেস স্ক্রিনশট প্রতিরোধকের প্রয়োজন


44

কখনও কখনও কোডে মন্তব্য করার চেয়ে বেশি ডকুমেন্টেশন লিখতে হবে। এবং কখনও কখনও, এই ব্যাখ্যাগুলির জন্য স্ক্রিনশট দরকার। কখনও কখনও এই জাতীয় স্ক্রিনশট পাওয়ার শর্তগুলি এত অদ্ভুত হয় যে আমি একজন বিকাশকারীকে আমার জন্য একটি স্ক্রিনশট নিতে বলি। কখনও কখনও স্ক্রিনশটটি আমার স্পেসিফিকেশনের সাথে ফিট করে না এবং আমাকে এটির আকার পরিবর্তন করতে হবে যাতে এটি দুর্দান্ত দেখায়।

যেমন আপনি দেখতে পাচ্ছেন, ম্যাজিক "লসলেস স্ক্রিনশট রেজাইজার" এর প্রয়োজনের পরিস্থিতি খুব কমই। যাইহোক, আমার জন্য মনে হয় এটি আমার প্রতিদিন প্রয়োজন। তবে এটি এখনও বিদ্যমান নেই।

আমি আপনাকে এখানে আগে পিসিজিতে দুর্দান্ত ভয়ঙ্কর গ্রাফিকাল ধাঁধা সমাধান করতে দেখেছি , তাই আমি অনুমান করি এটি আপনার জন্য বরং বিরক্তিকর ...

সবিস্তার বিবরণী

  • প্রোগ্রামটি ইনপুট হিসাবে একক উইন্ডোর স্ক্রিনশট নেয়
  • স্ক্রিনশটটি গ্লাসের প্রভাব বা অনুরূপ ব্যবহার করে না (যাতে আপনার যে কোনও পটভূমির মধ্য দিয়ে জ্বলজ্বল করে এমন জিনিসগুলি মোকাবেলা করার দরকার নেই)
  • ইনপুট ফাইল ফর্ম্যাটটি হ'ল পিএনজি (বা অন্য কোনও লসহীন ফর্ম্যাট যাতে আপনার সংকোচনের নিদর্শনগুলির সাথে কারবার করতে হবে না)
  • আউটপুট ফাইল ফর্ম্যাটটি ইনপুট ফাইল ফর্ম্যাটের মতো the
  • প্রোগ্রাম আউটপুট হিসাবে বিভিন্ন আকারের একটি স্ক্রিনশট তৈরি করে। সর্বনিম্ন প্রয়োজনীয়তা আকারে সঙ্কুচিত হচ্ছে।
  • ব্যবহারকারী প্রত্যাশিত আউটপুট আকার নির্দিষ্ট করতে হবে। যদি আপনি প্রদত্ত ইনপুট থেকে আপনার প্রোগ্রামটি ন্যূনতম আকারের উত্স তৈরি করতে পারে তবে এটি সহায়ক।
  • যদি কোনও মানুষের দ্বারা ব্যাখ্যা করা হয় তবে আউটপুট স্ক্রিনশটে কম তথ্য থাকতে হবে না। আপনি পাঠ্য বা চিত্রের সামগ্রী মুছে ফেলবেন না, তবে আপনি কেবল পটভূমির অঞ্চলগুলি সরাবেন। নীচে উদাহরণ দেখুন।
  • যদি প্রত্যাশিত আকারটি পাওয়া সম্ভব না হয় তবে প্রোগ্রামটি এটি নির্দেশ করে এবং কেবল কোনও ক্রাশ বা পরবর্তী বিজ্ঞপ্তি ছাড়াই তথ্য অপসারণ না করা উচিত।
  • যদি প্রোগ্রামটি সেই অঞ্চলগুলিকে নির্দেশ করে যা যাচাইকরণের কারণে সরিয়ে নেওয়া হবে, তবে এটির জনপ্রিয়তা বাড়ানো উচিত।
  • প্রোগ্রামটির জন্য আরও কিছু ব্যবহারকারীর ইনপুট প্রয়োজন হতে পারে, উদাহরণস্বরূপ অপ্টিমাইজেশনের জন্য প্রাথমিক পয়েন্টটি সনাক্ত করতে।

বিধি

এটি একটি জনপ্রিয়তা প্রতিযোগিতা। 2015-03-08 এ সর্বাধিক ভোটের উত্তর গৃহীত হবে।

উদাহরণ

উইন্ডোজ এক্সপি স্ক্রিনশট। আসল আকার: 1003x685 পিক্সেল।

এক্সপি স্ক্রিনশট বড়

উদাহরণস্বরূপ অঞ্চলগুলি (লাল: উল্লম্ব, হলুদ: অনুভূমিক) কোনও তথ্য (পাঠ্য বা চিত্র) না হারিয়ে সরিয়ে ফেলা যায়। মনে রাখবেন যে লাল বারটি সুসংগত নয়। এই উদাহরণটি সমস্ত সম্ভাব্য পিক্সেলগুলি নির্দেশ করে না যা সম্ভাব্যভাবে মুছে ফেলা হতে পারে।

এক্সপি স্ক্রিনশট অপসারণ সূচক

ক্ষতিহীনভাবে পুনরায় আকার দেওয়া হয়েছে: 783x424 পিক্সেল।

এক্সপি স্ক্রিনশট ছোট

উইন্ডোজ 10 এর স্ক্রিনশট। আসল আকার: 999x593 পিক্সেল।

উইন্ডোজ 10 এর স্ক্রিনশট বড়

সরানো যেতে পারে এমন অঞ্চলগুলির উদাহরণ Example

উইন্ডোজ 10 স্ক্রিনশট অপসারণ নির্দেশিত

ক্ষতিহীনভাবে স্ক্রিনশটটি আকার: 689x320 পিক্সেল।

মনে রাখবেন যে এটি ঠিক আছে যে শিরোনাম পাঠ্য ("ডাউনলোডগুলি") এবং "এই ফোল্ডারটি খালি" আরও কোনও কেন্দ্রীভূত নয়। অবশ্যই এটি কেন্দ্রিক হলে এটি আরও ভাল হবে এবং আপনার সমাধান যদি এটি সরবরাহ করে তবে এটি আরও জনপ্রিয় হওয়া উচিত।

উইন্ডোজ 10 স্ক্রিনশট ছোট


3
ফটোশপের " বিষয়বস্তু সম্পর্কে সচেতন স্কেলিং " বৈশিষ্ট্যটি মনে করিয়ে দেয় ।
শে

কি বিন্যাস ইনপুট হয়। আমরা কি কোনও মানক ইমেজ ফর্ম্যাট বেছে নিতে পারি?
HEGX64

@ থমাসডব্লু বলেছে "আমার ধারণা এটি একঘেয়ে হয়ে গেছে"। সত্য না. এটি ডায়াবোলিক্যাল।
লজিক নাইট

1
এই প্রশ্নটি যথেষ্ট মনোযোগ পায় না, প্রথম উত্তরটি আপত্তি করা হয়েছে কারণ এটি দীর্ঘ সময়ের জন্য একমাত্র উত্তর ছিল। ভোটের পরিমাণ এই মুহুর্তে বিভিন্ন উত্তরের জনপ্রিয়তার প্রতিনিধিত্ব করতে যথেষ্ট নয়। প্রশ্নটি কীভাবে আমরা আরও বেশি লোককে ভোট দিতে পারি? এমনকি আমি একটি উত্তরে ভোট দিয়েছি।
রল্ফ ツ

1
@ রল্ফ ツ: এই প্রশ্নটি থেকে আমি এখন পর্যন্ত যে খ্যাতি অর্জন করেছি তার 2/3 মূল্যের অনুগ্রহ শুরু করেছি। আমি আশা করি এটি যথেষ্ট ন্যায্য।
টমাস ওয়েলার

উত্তর:


29

পাইথন

ফাংশনটি delrowsএক অনুলিপি সারি বাদে সমস্ত মুছে ফেলে এবং ট্রান্সপোজড ইমেজটি ফেরত দেয়, এটি পুনরায় প্রয়োগ করে কলামগুলিও মুছে ফেলা হয় এবং এটি আবার স্থানান্তরিত করে। উপরন্তু thresholdনিয়ন্ত্রণ অনেক পিক্সেল দুই লাইনের জন্য পৃথক হতে পারে কিভাবে এখনও একই বিবেচনা করা

from scipy import misc
from pylab import *

im7 = misc.imread('win7.png')
im8 = misc.imread('win8.png')

def delrows(im, threshold=0):
    d = diff(im, axis=0)
    mask = where(sum((d!=0), axis=(1,2))>threshold)
    return transpose(im[mask], (1,0,2))

imsave('crop7.png', delrows(delrows(im7)))
imsave('crop8.png', delrows(delrows(im8)))

এখানে চিত্র বর্ণনা লিখুন
এখানে চিত্র বর্ণনা লিখুন

মধ্যে comparator Flipping maskথেকে >থেকে <=হবে পরিবর্তে আউটপুট মুছে এলাকায় যা বেশিরভাগ ফাঁকা স্থান আছে।

এখানে চিত্র বর্ণনা লিখুন এখানে চিত্র বর্ণনা লিখুন

গল্ফড (কারণ কেন নয়)
প্রতিটি পিক্সেলের তুলনা করার পরিবর্তে এটি কেবল যোগফলকে দেখায়, পার্শ্ব প্রতিক্রিয়া হিসাবে এটি স্ক্রিনশটটিকে গ্রেস্কেলতে রূপান্তরিত করে এবং উইন 8 এর অ্যাড্রেস বারের ডাউন-তীরের মতো সমষ্টি সংরক্ষণের অনুমতিতে সমস্যা হয় স্ক্রিনশট

from scipy import misc
from pylab import*
f=lambda M:M[where(diff(sum(M,1)))].T
imsave('out.png', f(f(misc.imread('in.png',1))),cmap='gray')

এখানে চিত্র বর্ণনা লিখুন
এখানে চিত্র বর্ণনা লিখুন


বাহ, এমনকি গল্ফড ... (আশা করি আপনি জানেন যে এটি একটি জনপ্রিয়তা প্রতিযোগিতা)
টমাস ওয়েলার

আপনি কি গল্ফ স্কোর অপসারণ করতে আপত্তি করবেন? এটি লোকেদের এই কোড গল্ফ মনে করে ছেড়ে যেতে পারে। ধন্যবাদ.
টমাস ওয়েলার

1
@ThomasW। স্কোরটি সরিয়ে এটিকে নীচে সরানো হয়েছে, দৃষ্টির বাইরে।
ডেনডেনডো

15

জাভা: নিখরচায় এবং সামগ্রী-সচেতন ফ্যালব্যাক চেষ্টা করুন

(এখন পর্যন্ত সেরা লসলেস ফলাফল!)

এক্সপি স্ক্রিনশট কাঙ্ক্ষিত আকার ছাড়াই ক্ষতিহীন

আমি যখন প্রথম এই প্রশ্নের দিকে তাকিয়েছিলাম তখন আমি ভেবেছিলাম এটি কোনও ধাঁধা বা চ্যালেঞ্জ নয় কেবলমাত্র কোনও প্রোগ্রামের প্রয়োজনের প্রয়োজন এবং এটির কোড;) তবে আমার দৃষ্টিভঙ্গির সমস্যাগুলি সমাধান করা আমার স্বভাবত তাই এই চ্যালেঞ্জটি চেষ্টা করার থেকে আমি নিজেকে আটকাতে পারি না could !

আমি নীচের পদ্ধতির এবং এলগরিদমের সংমিশ্রণ নিয়ে এসেছি।

সিউডো-কোডে এটি দেখতে এমন দেখাচ্ছে:

function crop(image, desired) {
    int sizeChange = 1;
    while(sizeChange != 0 and image.width > desired){

        Look for a repeating and connected set of lines (top to bottom) with a minimum of x lines
        Remove all the lines except for one
        sizeChange = image.width - newImage.width
        image = newImage;
    }
    if(image.width > desired){
        while(image.width > 2 and image.width > desired){
           Create a "pixel energy" map of the image
           Find the path from the top of the image to the bottom which "costs" the least amount of "energy"
           Remove the lowest cost path from the image
           image = newImage;
        }
    }
}

int desiredWidth = ?
int desiredHeight = ?
Image image = input;

crop(image, desiredWidth);
rotate(image, 90);
crop(image, desiredWidth);
rotate(image, -90);

ব্যবহৃত কৌশল:

  • তীব্র গ্রেস্কেল
  • প্রসারণ
  • সমান কলাম অনুসন্ধান এবং সরান
  • কয়লাস্তর-খোদাই
  • সোবেল প্রান্ত সনাক্তকরণ
  • থ্রেশহোল্ডিং

কার্যক্রম

প্রোগ্রামটি স্ক্রিনশটগুলি ক্ষতিহীন কাটতে পারে তবে সামগ্রী-সচেতন ক্রপিংয়ে ফ্যালব্যাকের বিকল্প রয়েছে যা 100% ক্ষতিহীন নয়। আরও ভাল ফলাফল অর্জন করার জন্য প্রোগ্রামের যুক্তিগুলি টুইট করা যেতে পারে।

দ্রষ্টব্য: প্রোগ্রামটি বিভিন্ন উপায়ে উন্নত করা যেতে পারে (আমার কাছে এতো অতিরিক্ত সময় নেই!)

যুক্তি

File name = file
Desired width = number > 0
Desired height = number > 0
Min slice width = number > 1
Compare threshold = number > 0
Use content aware = boolean
Max content aware cycles = number >= 0

কোড

import java.awt.Graphics2D;
import java.awt.image.BufferedImage;
import java.awt.image.ColorModel;
import java.io.File;
import java.io.IOException;
import javax.imageio.ImageIO;
import javax.swing.ImageIcon;
import javax.swing.JLabel;
import javax.swing.JOptionPane;

/**
 * @author Rolf Smit
 * Share and adapt as you like, but don't forget to credit the author!
 */
public class MagicWindowCropper {

    public static void main(String[] args) {
        if(args.length != 7){
            throw new IllegalArgumentException("At least 7 arguments are required: (file, desiredWidth, desiredHeight, minSliceSize, sliceThreshold, forceRemove, maxForceRemove)!");
        }

        File file = new File(args[0]);

        int minSliceSize = Integer.parseInt(args[3]); //4;
        int desiredWidth = Integer.parseInt(args[1]); //400;
        int desiredHeight = Integer.parseInt(args[2]); //400;

        boolean forceRemove = Boolean.parseBoolean(args[5]); //true
        int maxForceRemove = Integer.parseInt(args[6]); //40

        MagicWindowCropper.MATCH_THRESHOLD = Integer.parseInt(args[4]); //3;

        try {

            BufferedImage result = ImageIO.read(file);

            System.out.println("Horizontal cropping");

            //Horizontal crop
            result = doDuplicateColumnsMagic(result, minSliceSize, desiredWidth);
            if (result.getWidth() != desiredWidth && forceRemove) {
                result = doSeamCarvingMagic(result, maxForceRemove, desiredWidth);
            }

            result = getRotatedBufferedImage(result, false);


            System.out.println("Vertical cropping");

            //Vertical crop
            result = doDuplicateColumnsMagic(result, minSliceSize, desiredHeight);
            if (result.getWidth() != desiredHeight && forceRemove) {
                result = doSeamCarvingMagic(result, maxForceRemove, desiredHeight);
            }

            result = getRotatedBufferedImage(result, true);

            showBufferedImage("Result", result);

            ImageIO.write(result, "png", getNewFileName(file));
        } catch (IOException e) {
            e.printStackTrace();
        }
    }

    private static BufferedImage doSeamCarvingMagic(BufferedImage inputImage, int max, int desired) {
        System.out.println("Seam Carving magic:");

        int maxChange = Math.min(inputImage.getWidth() - desired, max);

        BufferedImage last = inputImage;
        int total = 0, change;
        do {
            int[][] energy = getPixelEnergyImage(last);
            BufferedImage out = removeLowestSeam(energy, last);

            change = last.getWidth() - out.getWidth();
            total += change;
            System.out.println("Carves removed: " + total);
            last = out;
        } while (change != 0 && total < maxChange);

        return last;
    }

    private static BufferedImage doDuplicateColumnsMagic(BufferedImage inputImage, int minSliceWidth, int desired) {
        System.out.println("Duplicate columns magic:");

        int maxChange = inputImage.getWidth() - desired;

        BufferedImage last = inputImage;
        int total = 0, change;
        do {
            BufferedImage out = removeDuplicateColumn(last, minSliceWidth, desired);

            change = last.getWidth() - out.getWidth();
            total += change;
            System.out.println("Columns removed: " + total);
            last = out;
        } while (change != 0 && total < maxChange);
        return last;
    }


    /*
     * Duplicate column methods
     */

    private static BufferedImage removeDuplicateColumn(BufferedImage inputImage, int minSliceWidth, int desiredWidth) {
        if (inputImage.getWidth() <= minSliceWidth) {
            throw new IllegalStateException("The image width is smaller than the minSliceWidth! What on earth are you trying to do?!");
        }

        int[] stamp = null;
        int sliceStart = -1, sliceEnd = -1;
        for (int x = 0; x < inputImage.getWidth() - minSliceWidth + 1; x++) {
            stamp = getHorizontalSliceStamp(inputImage, x, minSliceWidth);
            if (stamp != null) {
                sliceStart = x;
                sliceEnd = x + minSliceWidth - 1;
                break;
            }
        }

        if (stamp == null) {
            return inputImage;
        }

        BufferedImage out = deepCopyImage(inputImage);

        for (int x = sliceEnd + 1; x < inputImage.getWidth(); x++) {
            int[] row = getHorizontalSliceStamp(inputImage, x, 1);
            if (equalsRows(stamp, row)) {
                sliceEnd = x;
            } else {
                break;
            }
        }

        //Remove policy
        int canRemove = sliceEnd - (sliceStart + 1) + 1;
        int mayRemove = inputImage.getWidth() - desiredWidth;

        int dif = mayRemove - canRemove;
        if (dif < 0) {
            sliceEnd += dif;
        }

        int mustRemove = sliceEnd - (sliceStart + 1) + 1;
        if (mustRemove <= 0) {
            return out;
        }

        out = removeHorizontalRegion(out, sliceStart + 1, sliceEnd);
        out = removeLeft(out, out.getWidth() - mustRemove);
        return out;
    }

    private static BufferedImage removeHorizontalRegion(BufferedImage image, int startX, int endX) {
        int width = endX - startX + 1;

        if (endX + 1 > image.getWidth()) {
            endX = image.getWidth() - 1;
        }
        if (endX < startX) {
            throw new IllegalStateException("Invalid removal parameters! Wow this error message is genius!");
        }

        BufferedImage out = deepCopyImage(image);

        for (int x = endX + 1; x < image.getWidth(); x++) {
            for (int y = 0; y < image.getHeight(); y++) {
                out.setRGB(x - width, y, image.getRGB(x, y));
                out.setRGB(x, y, 0xFF000000);
            }
        }
        return out;
    }

    private static int[] getHorizontalSliceStamp(BufferedImage inputImage, int startX, int sliceWidth) {
        int[] initial = new int[inputImage.getHeight()];
        for (int y = 0; y < inputImage.getHeight(); y++) {
            initial[y] = inputImage.getRGB(startX, y);
        }
        if (sliceWidth == 1) {
            return initial;
        }
        for (int s = 1; s < sliceWidth; s++) {
            int[] row = new int[inputImage.getHeight()];
            for (int y = 0; y < inputImage.getHeight(); y++) {
                row[y] = inputImage.getRGB(startX + s, y);
            }

            if (!equalsRows(initial, row)) {
                return null;
            }
        }
        return initial;
    }

    private static int MATCH_THRESHOLD = 3;

    private static boolean equalsRows(int[] left, int[] right) {
        for (int i = 0; i < left.length; i++) {

            int rl = (left[i]) & 0xFF;
            int gl = (left[i] >> 8) & 0xFF;
            int bl = (left[i] >> 16) & 0xFF;

            int rr = (right[i]) & 0xFF;
            int gr = (right[i] >> 8) & 0xFF;
            int br = (right[i] >> 16) & 0xFF;

            if (Math.abs(rl - rr) > MATCH_THRESHOLD
                    || Math.abs(gl - gr) > MATCH_THRESHOLD
                    || Math.abs(bl - br) > MATCH_THRESHOLD) {
                return false;
            }
        }
        return true;
    }


    /*
     * Seam carving methods
     */

    private static BufferedImage removeLowestSeam(int[][] input, BufferedImage image) {
        int lowestValue = Integer.MAX_VALUE; //Integer overflow possible when image height grows!
        int lowestValueX = -1;

        // Here be dragons
        for (int x = 1; x < input.length - 1; x++) {
            int seamX = x;
            int value = input[x][0];
            for (int y = 1; y < input[x].length; y++) {
                if (seamX < 1) {
                    int top = input[seamX][y];
                    int right = input[seamX + 1][y];
                    if (top <= right) {
                        value += top;
                    } else {
                        seamX++;
                        value += right;
                    }
                } else if (seamX > input.length - 2) {
                    int top = input[seamX][y];
                    int left = input[seamX - 1][y];
                    if (top <= left) {
                        value += top;
                    } else {
                        seamX--;
                        value += left;
                    }
                } else {
                    int left = input[seamX - 1][y];
                    int top = input[seamX][y];
                    int right = input[seamX + 1][y];

                    if (top <= left && top <= right) {
                        value += top;
                    } else if (left <= top && left <= right) {
                        seamX--;
                        value += left;
                    } else {
                        seamX++;
                        value += right;
                    }
                }
            }
            if (value < lowestValue) {
                lowestValue = value;
                lowestValueX = x;
            }
        }

        BufferedImage out = deepCopyImage(image);

        int seamX = lowestValueX;
        shiftRow(out, seamX, 0);
        for (int y = 1; y < input[seamX].length; y++) {
            if (seamX < 1) {
                int top = input[seamX][y];
                int right = input[seamX + 1][y];
                if (top <= right) {
                    shiftRow(out, seamX, y);
                } else {
                    seamX++;
                    shiftRow(out, seamX, y);
                }
            } else if (seamX > input.length - 2) {
                int top = input[seamX][y];
                int left = input[seamX - 1][y];
                if (top <= left) {
                    shiftRow(out, seamX, y);
                } else {
                    seamX--;
                    shiftRow(out, seamX, y);
                }
            } else {
                int left = input[seamX - 1][y];
                int top = input[seamX][y];
                int right = input[seamX + 1][y];

                if (top <= left && top <= right) {
                    shiftRow(out, seamX, y);
                } else if (left <= top && left <= right) {
                    seamX--;
                    shiftRow(out, seamX, y);
                } else {
                    seamX++;
                    shiftRow(out, seamX, y);
                }
            }
        }

        return removeLeft(out, out.getWidth() - 1);
    }

    private static void shiftRow(BufferedImage image, int startX, int y) {
        for (int x = startX; x < image.getWidth() - 1; x++) {
            image.setRGB(x, y, image.getRGB(x + 1, y));
        }
    }

    private static int[][] getPixelEnergyImage(BufferedImage image) {

        // Convert Image to gray scale using the luminosity method and add extra
        // edges for the Sobel filter
        int[][] grayScale = new int[image.getWidth() + 2][image.getHeight() + 2];
        for (int x = 0; x < image.getWidth(); x++) {
            for (int y = 0; y < image.getHeight(); y++) {
                int rgb = image.getRGB(x, y);
                int r = (rgb >> 16) & 0xFF;
                int g = (rgb >> 8) & 0xFF;
                int b = (rgb & 0xFF);
                int luminosity = (int) (0.21 * r + 0.72 * g + 0.07 * b);
                grayScale[x + 1][y + 1] = luminosity;
            }
        }

        // Sobel edge detection
        final double[] kernelHorizontalEdges = new double[] { 1, 2, 1, 0, 0, 0, -1, -2, -1 };
        final double[] kernelVerticalEdges = new double[] { 1, 0, -1, 2, 0, -2, 1, 0, -1 };

        int[][] energyImage = new int[image.getWidth()][image.getHeight()];

        for (int x = 1; x < image.getWidth() + 1; x++) {
            for (int y = 1; y < image.getHeight() + 1; y++) {

                int k = 0;
                double horizontal = 0;
                for (int ky = -1; ky < 2; ky++) {
                    for (int kx = -1; kx < 2; kx++) {
                        horizontal += ((double) grayScale[x + kx][y + ky] * kernelHorizontalEdges[k]);
                        k++;
                    }
                }
                double vertical = 0;
                k = 0;
                for (int ky = -1; ky < 2; ky++) {
                    for (int kx = -1; kx < 2; kx++) {
                        vertical += ((double) grayScale[x + kx][y + ky] * kernelVerticalEdges[k]);
                        k++;
                    }
                }

                if (Math.sqrt(horizontal * horizontal + vertical * vertical) > 127) {
                    energyImage[x - 1][y - 1] = 255;
                } else {
                    energyImage[x - 1][y - 1] = 0;
                }
            }
        }

        //Dilate the edge detected image a few times for better seaming results
        //Current value is just 1...
        for (int i = 0; i < 1; i++) {
            dilateImage(energyImage);
        }
        return energyImage;
    }

    private static void dilateImage(int[][] image) {
        for (int x = 0; x < image.length; x++) {
            for (int y = 0; y < image[x].length; y++) {
                if (image[x][y] == 255) {
                    if (x > 0 && image[x - 1][y] == 0) {
                        image[x - 1][y] = 2; //Note: 2 is just a placeholder value
                    }
                    if (y > 0 && image[x][y - 1] == 0) {
                        image[x][y - 1] = 2;
                    }
                    if (x + 1 < image.length && image[x + 1][y] == 0) {
                        image[x + 1][y] = 2;
                    }
                    if (y + 1 < image[x].length && image[x][y + 1] == 0) {
                        image[x][y + 1] = 2;
                    }
                }
            }
        }
        for (int x = 0; x < image.length; x++) {
            for (int y = 0; y < image[x].length; y++) {
                if (image[x][y] == 2) {
                    image[x][y] = 255;
                }
            }
        }
    }

    /*
     * Utilities
     */

    private static void showBufferedImage(String windowTitle, BufferedImage image) {
        JOptionPane.showMessageDialog(null, new JLabel(new ImageIcon(image)), windowTitle, JOptionPane.PLAIN_MESSAGE, null);
    }

    private static BufferedImage deepCopyImage(BufferedImage input) {
        ColorModel cm = input.getColorModel();
        return new BufferedImage(cm, input.copyData(null), cm.isAlphaPremultiplied(), null);
    }

    private static final BufferedImage getRotatedBufferedImage(BufferedImage img, boolean back) {
        double oldW = img.getWidth(), oldH = img.getHeight();
        double newW = img.getHeight(), newH = img.getWidth();

        BufferedImage out = new BufferedImage((int) newW, (int) newH, img.getType());
        Graphics2D g = out.createGraphics();
        g.translate((newW - oldW) / 2.0, (newH - oldH) / 2.0);
        g.rotate(Math.toRadians(back ? -90 : 90), oldW / 2.0, oldH / 2.0);
        g.drawRenderedImage(img, null);
        g.dispose();
        return out;
    }

    private static BufferedImage removeLeft(BufferedImage image, int startX) {
        int removeWidth = image.getWidth() - startX;

        BufferedImage out = new BufferedImage(image.getWidth() - removeWidth,
                image.getHeight(), image.getType());

        for (int x = 0; x < startX; x++) {
            for (int y = 0; y < out.getHeight(); y++) {
                out.setRGB(x, y, image.getRGB(x, y));
            }
        }
        return out;
    }

    private static File getNewFileName(File in) {
        String name = in.getName();
        int i = name.lastIndexOf(".");
        if (i != -1) {
            String ext = name.substring(i);
            String n = name.substring(0, i);
            return new File(in.getParentFile(), n + "-cropped" + ext);
        } else {
            return new File(in.getParentFile(), name + "-cropped");
        }
    }
}

ফলাফল


এক্সপি স্ক্রিনশটটি কাঙ্ক্ষিত আকার ছাড়াই ক্ষতিহীন

যুক্তি: "image.png" 1 1 5 10 ভুয়া 0

ফলাফল: 836 x 323

এক্সপি স্ক্রিনশট কাঙ্ক্ষিত আকার ছাড়াই ক্ষতিহীন


এক্সপি স্ক্রিনশট 800x600 এ

যুক্তি: "image.png" 800 600 6 10 সত্য 60

ফলাফল: 800 x 600

অ্যালগরিদমটি কন্টেন্ট-সচেতন অপসারণের পিছনে পড়ে যাওয়ার চেয়ে লসলেস অ্যালগরিদম প্রায় 155 অনুভূমিক রেখাগুলি সরিয়ে ফেলে যার ফলে কিছু শিল্পকর্ম দেখা যায়।

এক্সপি স্ক্রিনশট 800x600 এ


উইন্ডোজ 10 এর স্ক্রিনশট 700x300 এ

যুক্তি: "image.png" 700 300 6 10 সত্য 60

ফলাফল: 700 x 300

লসলেস অ্যালগরিদম 270 অনুভূমিক রেখাগুলি সরিয়ে দেয় যেহেতু অ্যালগোরিদম কন্টেন্ট-সচেতন অপসারণে ফিরে আসে যা অন্য 29 টিকে সরিয়ে দেয় Ver কেবল উল্লম্বভাবে দোষহীন অ্যালগরিদম ব্যবহার করা হয়।

উইন্ডোজ 10 এর স্ক্রিনশট 700x300 এ


উইন্ডোজ 10 স্ক্রিনশট কন্টেন্ট-সচেতন 400x200 (পরীক্ষা)

যুক্তি: "image.png" 400 200 5 10 সত্য 600

ফলাফল: 400 x 200

ফলস্বরূপ চিত্রটি কীভাবে সামগ্রী-সচেতন বৈশিষ্ট্যটির তীব্র ব্যবহারের পরে দেখাবে তা দেখার জন্য এটি একটি পরীক্ষা ছিল। ফলাফলটি ব্যাপকভাবে ক্ষতিগ্রস্থ হয়েছে তবে অচেনা নয়।

উইন্ডোজ 10 স্ক্রিনশট কন্টেন্ট-সচেতন 400x200 (পরীক্ষা)



প্রথম আউটপুট সম্পূর্ণ ছাঁটাই হয় না। ডান দিক থেকে আমাকে এতটুকু কেটে যেতে পারে
অপ্টিমাইজার

কারণ এটি (আমার প্রোগ্রামের) আর্গুমেন্টগুলি বলেছে যে এটি এটিকে 800 পিক্সেলের চেয়ে আরও অনুকূল করা উচিত নয় :)
রল্ফ olf

এই পপকন হিসাবে, আপনার সম্ভবত সেরা ফলাফলগুলি দেখানো উচিত :)
অপ্টিমাইজার

আমার প্রোগ্রামটি প্রাথমিকভাবে অন্য উত্তরের মতোই হয় তবে এটি আরও ডাউন-স্কেলিংয়ের জন্য একটি সামগ্রী-সচেতন ফাংশনও রয়েছে। এটিতে পছন্দসই প্রস্থ এবং উচ্চতায় ক্রপ করার বিকল্প রয়েছে (প্রশ্ন দেখুন)।
রল্ফ ツ

3

সি #, অ্যালগরিদম যেমন আমি ম্যানুয়ালি করব

এটা আমার প্রথম চিত্র প্রক্রিয়াকরণ প্রোগ্রাম এবং এটি সমস্ত সঙ্গে বাস্তবায়ন করে একটি সময় লেগেছিল LockBitsকাপড় ইত্যাদি কিন্তু আমি এটি দ্রুত হতে চেয়েছিলেন (ব্যবহার Parallel.For) একটি প্রায় তাত্ক্ষণিক প্রতিক্রিয়া জন্য।

মূলত আমার অ্যালগরিদম আমি কীভাবে স্ক্রিনশট থেকে পিক্সেলগুলি ম্যানুয়ালি সরিয়ে ফেলব তা পর্যবেক্ষণের ভিত্তিতে:

  • আমি ডান প্রান্ত থেকে শুরু করছি, কারণ অব্যবহৃত পিক্সেল বেশি হওয়ার সম্ভাবনা বেশি।
  • সঠিকভাবে সিস্টেম বোতামগুলি ক্যাপচার করার জন্য আমি প্রান্ত সনাক্তকরণের জন্য একটি প্রান্তিক সংজ্ঞা নির্ধারণ করি। উইন্ডোজ 10 স্ক্রিনশটের জন্য, 48 পিক্সেলের একটি প্রান্তিকর ভাল কাজ করে।
  • প্রান্তটি সনাক্ত হওয়ার পরে (নীচে লাল বর্ণে চিহ্নিত), আমি একই রঙের পিক্সেল খুঁজছি। আমি পাওয়া পিক্সেলগুলির ন্যূনতম সংখ্যা নিয়েছি এবং এটি সমস্ত সারিতে চিহ্নিত করা হয়েছে (চিহ্নিত ভায়োলেট)।
  • তারপরে আমি আবার প্রান্ত সনাক্তকরণ (লাল চিহ্নিত), একই রঙের পিক্সেল (নীল, তারপর সবুজ, পরে হলুদ) এবং আরও কিছু দিয়ে আবার শুরু করব

এই মুহুর্তে আমি এটি কেবল অনুভূমিকভাবে করি। উল্লম্ব ফলাফল একই অ্যালগরিদম ব্যবহার করতে পারে এবং 90 ° ঘোরানো চিত্রটিতে কাজ করতে পারে, তাই তাত্ত্বিকভাবে এটি সম্ভব।

ফলাফল

এটি সনাক্ত করা অঞ্চলগুলি সহ আমার অ্যাপ্লিকেশনটির একটি স্ক্রিনশট:

লসলেস স্ক্রিনশট রোধাকারী

এবং এটি উইন্ডোজ 10 স্ক্রিনশট এবং 48 পিক্সেল প্রান্তিকের জন্য ফলাফল result আউটপুটটি 681 পিক্সেল প্রশস্ত। দুর্ভাগ্যক্রমে এটি নিখুঁত নয় ("অনুসন্ধান ডাউনলোডগুলি" এবং কিছু উল্লম্ব কলাম বারগুলি দেখুন)।

উইন্ডোজ 10 ফলাফল, 48 পিক্সেল থ্রেশহোল্ড

এবং অন্য একটি 64৪ পিক্সেল প্রান্তিক (৫ (7 পিক্সেল প্রশস্ত) সহ। এটি আরও ভাল দেখায়।

উইন্ডোজ 10 ফলাফল, 64 পিক্সেল থ্রেশহোল্ড

সামগ্রিক ফলাফল সমস্ত নীচ থেকে ক্রপগুলিতে রোটেশন প্রয়োগ করে (567x304 পিক্সেল)।

উইন্ডোজ 10 ফলাফল, 64 পিক্সেল থ্রেশহোল্ড, ঘোরানো

উইন্ডোজ এক্সপি-র জন্য আমার কোডটি কিছুটা পরিবর্তন করা দরকার যেহেতু পিক্সেলগুলি ঠিক সমান নয়। আমি 8 (আরজিবি মানের পার্থক্য) এর অনুরূপ প্রান্তিকতা প্রয়োগ করছি। কলামগুলিতে কিছু শিল্পকর্ম নোট করুন।

উইন্ডোজ এক্সপি স্ক্রিনশট সহ লোসলেস স্ক্রিনশট রোধকারী

উইন্ডোজ এক্সপি ফলাফল

কোড

ঠিক আছে, আমার ইমেজ প্রসেসিংয়ের প্রথম প্রচেষ্টা। খুব ভাল দেখাচ্ছে না, তাই না? এটি কেবলমাত্র মূল অ্যালগরিদমকে তালিকাবদ্ধ করে, ইউআই নয় এবং 90 ° ঘূর্ণন নয়।

using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Drawing;
using System.Linq;
using System.Threading.Tasks;

namespace LosslessScreenshotResizer.BL
{
    internal class PixelAreaSearcher
    {
        private readonly Bitmap _originalImage;

        private readonly int _edgeThreshold;
        readonly Color _edgeColor = Color.FromArgb(128, 255, 0, 0);
        readonly Color[] _iterationIndicatorColors =
        {
            Color.FromArgb(128, 0, 0, 255), 
            Color.FromArgb(128, 0, 255, 255), 
            Color.FromArgb(128, 0, 255, 0),
            Color.FromArgb(128, 255, 255, 0)
        };

        public PixelAreaSearcher(Bitmap originalImage, int edgeThreshold)
        {
            _originalImage = originalImage;
            _edgeThreshold = edgeThreshold;

            // cache width and height. Also need to do that because of some GDI exceptions during LockBits
            _imageWidth = _originalImage.Width;
            _imageHeight = _originalImage.Height;            
        }

        public Bitmap SearchHorizontal()
        {
            return Search();
        }

        /// <summary>
        /// Find areas of pixels to keep and to remove. You can get that information via <see cref="PixelAreas"/>.
        /// The result of this operation is a bitmap of the original picture with an overlay of the areas found.
        /// </summary>
        /// <returns></returns>
        private unsafe Bitmap Search()
        {
            // FastBitmap is a wrapper around Bitmap with LockBits enabled for fast operation.
            var input = new FastBitmap(_originalImage);
            // transparent overlay
            var overlay = new FastBitmap(_originalImage.Width, _originalImage.Height);

            _pixelAreas = new List<PixelArea>(); // save the raw data for later so that the image can be cropped
            int startCoordinate = _imageWidth - 1; // start at the right edge
            int iteration = 0; // remember the iteration to apply different colors
            int minimum;
            do
            {
                var indicatorColor = GetIterationColor(iteration);

                // Detect the edge which is not removable
                var edgeStartCoordinates = new PixelArea(_imageHeight) {AreaType = AreaType.Keep};
                Parallel.For(0, _imageHeight, y =>
                {
                    edgeStartCoordinates[y] = DetectEdge(input, y, overlay, _edgeColor, startCoordinate);
                }
                    );
                _pixelAreas.Add(edgeStartCoordinates);

                // Calculate how many pixels can theoretically be removed per line
                var removable = new PixelArea(_imageHeight) {AreaType = AreaType.Dummy};
                Parallel.For(0, _imageHeight, y =>
                {
                    removable[y] = CountRemovablePixels(input, y, edgeStartCoordinates[y]);
                }
                    );

                // Calculate the practical limit
                // We can only remove the same amount of pixels per line, otherwise we get a non-rectangular image
                minimum = removable.Minimum;
                Debug.WriteLine("Can remove {0} pixels", minimum);

                // Apply the practical limit: calculate the start coordinates of removable areas
                var removeStartCoordinates = new PixelArea(_imageHeight) { AreaType = AreaType.Remove };
                removeStartCoordinates.Width = minimum;
                for (int y = 0; y < _imageHeight; y++) removeStartCoordinates[y] = edgeStartCoordinates[y] - minimum;
                _pixelAreas.Add(removeStartCoordinates);

                // Paint the practical limit onto the overlay for demo purposes
                Parallel.For(0, _imageHeight, y =>
                {
                    PaintRemovableArea(y, overlay, indicatorColor, minimum, removeStartCoordinates[y]);
                }
                    );

                // Move the left edge before starting over
                startCoordinate = removeStartCoordinates.Minimum;
                var remaining = new PixelArea(_imageHeight) { AreaType = AreaType.Keep };
                for (int y = 0; y < _imageHeight; y++) remaining[y] = startCoordinate;
                _pixelAreas.Add(remaining);

                iteration++;
            } while (minimum > 1);


            input.GetBitmap(); // TODO HACK: release Lockbits on the original image 
            return overlay.GetBitmap();
        }

        private Color GetIterationColor(int iteration)
        {
            return _iterationIndicatorColors[iteration%_iterationIndicatorColors.Count()];
        }

        /// <summary>
        /// Find a minimum number of contiguous pixels from the right side of the image. Everything behind that is an edge.
        /// </summary>
        /// <param name="input">Input image to get pixel data from</param>
        /// <param name="y">The row to be analyzed</param>
        /// <param name="output">Output overlay image to draw the edge on</param>
        /// <param name="edgeColor">Color for drawing the edge</param>
        /// <param name="startCoordinate">Start coordinate, defining the maximum X</param>
        /// <returns>X coordinate where the edge starts</returns>
        private int DetectEdge(FastBitmap input, int y, FastBitmap output, Color edgeColor, int startCoordinate)
        {
            var repeatCount = 0;
            var lastColor = Color.DodgerBlue;
            int x;

            for (x = startCoordinate; x >= 0; x--)
            {
                var currentColor = input.GetPixel(x, y);
                if (almostEquals(lastColor,currentColor))
                {
                    repeatCount++;
                }
                else
                {
                    lastColor = currentColor;
                    repeatCount = 0;
                    for (int i = x; i < startCoordinate; i++)
                    {
                        output.SetPixel(i,y,edgeColor);
                    }
                }

                if (repeatCount > _edgeThreshold)
                {
                    return x + _edgeThreshold;
                }
            }
            return repeatCount;
        }

        /// <summary>
        /// Counts the number of contiguous pixels in a row, starting on the right and going to the left
        /// </summary>
        /// <param name="input">Input image to get pixels from</param>
        /// <param name="y">The current row</param>
        /// <param name="startingCoordinate">X coordinate to start from</param>
        /// <returns>Number of equal pixels found</returns>
        private int CountRemovablePixels(FastBitmap input, int y, int startingCoordinate)
        {
            var lastColor = input.GetPixel(startingCoordinate, y);
            for (int x=startingCoordinate; x >= 0; x--)
            {
                var currentColor = input.GetPixel(x, y);
                if (!almostEquals(currentColor,lastColor)) 
                {
                    return startingCoordinate-x; 
                }
            }
            return startingCoordinate;
        }

        /// <summary>
        /// Calculates color equality.
        /// Workaround for Windows XP screenshots which do not have 100% equal pixels.
        /// </summary>
        /// <returns>True if the RBG value is similar (maximum R+G+B difference is 8)</returns>
        private bool almostEquals(Color c1, Color c2)
        {
            int r = c1.R;
            int g = c1.G;
            int b = c1.B;
            int diff = (Math.Abs(r - c2.R) + Math.Abs(g - c2.G) + Math.Abs(b - c2.B));
            return (diff < 8) ;
        }

        /// <summary>
        /// Paint pixels that can be removed, starting at the X coordinate and painting to the right
        /// </summary>
        /// <param name="y">The current row</param>
        /// <param name="output">Overlay output image to draw on</param>
        /// <param name="removableColor">Color to use for drawing</param>
        /// <param name="width">Number of pixels that can be removed</param>
        /// <param name="start">Starting coordinate to begin drawing</param>
        private void PaintRemovableArea(int y, FastBitmap output, Color removableColor, int width, int start)
        {
            for(int i=start;i<start+width;i++)
            {
                output.SetPixel(i, y, removableColor);
            }
        }

        private readonly int _imageHeight;
        private readonly int _imageWidth;
        private List<PixelArea> _pixelAreas;

        public List<PixelArea> PixelAreas
        {
            get { return _pixelAreas; }
        }
    }
}

1
+1 আকর্ষণীয় পদ্ধতির, আমি এটি পছন্দ করি! মজা হবে যদি আমার এবং আপনার মত এখানে পোস্ট করা কিছু অ্যালগরিদম অনুকূল ফলাফল অর্জনের জন্য একত্রিত হয়। সম্পাদনা: সি # পড়ার জন্য একটি দৈত্য, আমি সর্বদা নিশ্চিত নই যে কোনও কিছু ক্ষেত্র বা যুক্তিযুক্ত কোনও ফাংশন / গেটর কিনা I'm
রল্ফ ツ

1

হ্যাস্কেল, সদৃশ অনুক্রমের লাইনগুলি সাদামাটা অপসারণ ব্যবহার করে

দুর্ভাগ্যক্রমে, এই মডিউলটি কেবল খুব জেনেরিক প্রকারের সাথে একটি ফাংশন সরবরাহ করে Eq a => [[a]] -> [[a]], যেহেতু হাস্কেলতে চিত্র ফাইলগুলি কীভাবে সম্পাদনা করতে হবে সে সম্পর্কে আমার কোনও ধারণা নেই, তবে আমি নিশ্চিত যে পিএনজি চিত্রটি কোনও [[Color]]মানকে ট্রান্সফর্ম করা সম্ভব এবং আমি কল্পনা instance Eq Colorকরতে পারি সহজেই নির্ধারণযোগ্য।

প্রশ্নে ফাংশন হয় resizeL

কোড:

import Data.List

nubSequential []    = []
nubSequential (a:b) = a : g a b where
 g x (h:t)  | x == h =     g x t
            | x /= h = h : g h t
 g x []     = []

resizeL     = nubSequential . transpose . nubSequential . transpose

ব্যাখ্যা:

দ্রষ্টব্য: a : b মানে তালিকার ধরণের তালিকার সাথে উপসর্গযুক্ত উপাদান , যার ফলে একটি তালিকা তৈরি হয়। এটি তালিকার মৌলিক নির্মাণ। খালি তালিকা বোঝায়।aa[]

দ্রষ্টব্য: a :: b অর্থ aপ্রকারের b। উদাহরণস্বরূপ, যদি a :: k, তবে (a : []) :: [k], যেখানে ধরণের [x]জিনিসযুক্ত একটি তালিকা চিহ্নিত করে x
এর অর্থ হ'ল (:)নিজেই, কোনও যুক্তি ছাড়াই :: a -> [a] -> [a],। ->কিছু কিছু থেকে একটি ফাংশন উল্লেখ করে।

import Data.Listকেবল কিছু কাজ কিছু অন্যান্য ব্যক্তিদের আমাদের জন্য করেনি পায় এবং তাদের rewriting ছাড়া তাদের ফাংশন ব্যবহার করতে দেয়।

প্রথমে একটি ফাংশন সংজ্ঞায়িত করুন nubSequential :: Eq a => [a] -> [a]
এই ফাংশনটি একই তালিকার পরবর্তী উপাদানগুলিকে সরিয়ে দেয়।
তাই nubSequential [1, 2, 2, 3] === [1, 2, 3],। আমরা এখন এই ফাংশনটি সংক্ষেপে করব nS

যদি nSখালি তালিকায় প্রয়োগ করা হয়, কিছুই করা যায় না, এবং আমরা খালি তালিকাটি সহজেই ফিরিয়ে দিই।

যদি nSবিষয়বস্তু সহ কোনও তালিকায় প্রয়োগ করা হয়, তবে প্রকৃত প্রসেসিং করা যায়। এর জন্য, আমাদের whereপুনরুক্তি ব্যবহার করতে এখানে একটি ক্লাসে একটি দ্বিতীয় ফাংশন প্রয়োজন , কারণ আমাদের nSসাথে কোনও উপাদানটির সাথে তুলনা করতে ট্র্যাক রাখা হয় না।
আমরা এই ফাংশনটির নামকরণ করি g। এটি প্রদত্ত তালিকার শীর্ষের সাথে তার প্রথম যুক্তিটির তুলনা করে এবং মাথাটি যদি তা মেলে তবে তারা পুরানো প্রথম যুক্তির সাথে লেজে নিজেকে কল করে কাজ করে। যদি তারা তা না করে তবে এটি মাথাটি লেজের উপরে সংযোজন করে, নতুন প্রথম যুক্তি হিসাবে মাথা দিয়ে itselfুকে পড়ে।
ব্যবহার করার জন্য g, আমরা এটিকে nSদুটি আর্গুমেন্ট হিসাবে আর্গুমেন্টের মাথা এবং লেজটিকে দেই ।

nSএখন টাইপ Eq a => [a] -> [a], একটি তালিকা গ্রহণ এবং একটি তালিকা ফিরে। এটি প্রয়োজনীয় যে আমরা ফাংশন সংজ্ঞায় এটি করা হয়ে থাকে হিসাবে উপাদানগুলির মধ্যে সমতা পরীক্ষা করতে পারি।

তারপরে, আমরা ফাংশনগুলি রচনা করি nSএবং অপারেটরটি transposeব্যবহার করি (.)
ফাংশন রচনা নিম্নলিখিত অর্থ: (f . g) x = f (g (x))

আমাদের উদাহরণে, transposeএকটি টেবিলটি 90 rot ঘোরান, nSতালিকার সমস্ত অনুক্রমিক সমতুল্য উপাদানগুলি সরিয়ে ফেলেন, এক্ষেত্রে অন্যান্য তালিকাগুলি (এটি একটি টেবিলটি কী) transposeএটি আবার ঘোরান এবং nSআবার ক্রমযুক্ত সমান উপাদানগুলি সরান। এটি মূলত পরবর্তী ডুপ্লিকেট সারিতে একটি কলাম সরিয়ে ফেলছে।

এটি সম্ভব কারণ কারণ যদি aসাম্যতার জন্য পরীক্ষাযোগ্য হয় ( instance Eq a) তবে তাও [a]
সংক্ষেপে:instance Eq a => Eq [a]

আমাদের সাইট ব্যবহার করে, আপনি স্বীকার করেছেন যে আপনি আমাদের কুকি নীতি এবং গোপনীয়তা নীতিটি পড়েছেন এবং বুঝতে পেরেছেন ।
Licensed under cc by-sa 3.0 with attribution required.