{
        Preconditions.checkState(window != null, "instance was detached, can't do much after that");
        Dimension size = configPairs.find(keySize(), null);
        if (size == null) {
            return false;
        }
        window.setSize(size);
        return true;
    }

/**
 * ****************************************************************************
 *  PGPTool is a desktop application for pgp encryption/decryption
 *
 * ****************************************************************************
 */
package org.pgptool.gui.ui.tools.geometrymemory;

import java.awt.Dimension;
import java.awt.Window;
import java.awt.event.ComponentAdapter;
import java.awt.event.ComponentEvent;
import java.util.concurrent.ScheduledExecutorService;
import java.util.concurrent.ScheduledFuture;
import java.util.concurrent.TimeUnit;
import org.pgptool.gui.configpairs.api.ConfigPairs;
import org.springframework.util.StringUtils;
import com.google.common.base.Preconditions;

public class WindowGeometryPersisterImpl extends ComponentAdapter implements WindowGeometryPersister {

    static final int MINIMUM_SANE_SIZE_VALUE = 10;

    private static final long DELAY = 500;

    private static final TimeUnit DELAY_TIME_UNIT = TimeUnit.MILLISECONDS;

    private ConfigPairs configPairs;

    private ScheduledExecutorService scheduledExecutorService;

    private Window window;

    private String keyId;

    private ScheduledFuture<?> sizeSetter;

    /**
     * @param scheduledExecutorService it's used to dealy interraction with
     *                                 configPairs to avoid spamming it with values
     *                                 while user still dragging element
     */
    public WindowGeometryPersisterImpl(Window window, String windowId, ConfigPairs configPairs, ScheduledExecutorService scheduledExecutorService) {
        Preconditions.checkArgument(scheduledExecutorService != null);
        Preconditions.checkArgument(window != null);
        Preconditions.checkArgument(StringUtils.hasText(windowId));
        Preconditions.checkArgument(configPairs != null);
        this.window = window;
        // version to version application windows sizes might change
        this.keyId = windowId + "_" + window.getMinimumSize();
        this.configPairs = configPairs;
        this.scheduledExecutorService = scheduledExecutorService;
        window.addComponentListener(this);
    }

    @Override
    public boolean restoreSize()

    // @Override
    // Preconditions.checkState(window != null, "instance was detached, can't do
    // much after that");
    @Override
    public void componentResized(ComponentEvent e) {
        if (window == null) {
            return;
        }
        Dimension size = window.getSize();
        if (size.getWidth() < MINIMUM_SANE_SIZE_VALUE || size.getHeight() < MINIMUM_SANE_SIZE_VALUE) {
            return;
        }
        if (sizeSetter != null) {
            sizeSetter.cancel(true);
        }
        sizeSetter = scheduledExecutorService.schedule(() -> configPairs.put(keySize(), window.getSize()), DELAY, DELAY_TIME_UNIT);
    }

    private String keySize() {
        return keyId + "_size";
    }

    // @Override
    // locationSetter = scheduledExecutorService
    @Override
    public boolean isAttached() {
        return window != null;
    }

    @Override
    public void detach() {
        Preconditions.checkState(window != null, "instance was detached, can't do much after that");
        window.removeComponentListener(this);
        window = null;
    }
}


        Preconditions.checkState(window != null, "instance was detached, can't do much after that");
        Dimension size = configPairs.find(keySize(), null);

/**
 * ****************************************************************************
 *  PGPTool is a desktop application for pgp encryption/decryption
 *
 * ****************************************************************************
 */
package org.pgptool.gui.ui.tools.geometrymemory;

import java.awt.Dimension;
import java.awt.Window;
import java.awt.event.ComponentAdapter;
import java.awt.event.ComponentEvent;
import java.util.concurrent.ScheduledExecutorService;
import java.util.concurrent.ScheduledFuture;
import java.util.concurrent.TimeUnit;
import org.pgptool.gui.configpairs.api.ConfigPairs;
import org.springframework.util.StringUtils;
import com.google.common.base.Preconditions;

public class WindowGeometryPersisterImpl extends ComponentAdapter implements WindowGeometryPersister {

    static final int MINIMUM_SANE_SIZE_VALUE = 10;

    private static final long DELAY = 500;

    private static final TimeUnit DELAY_TIME_UNIT = TimeUnit.MILLISECONDS;

    private ConfigPairs configPairs;

    private ScheduledExecutorService scheduledExecutorService;

    private Window window;

    private String keyId;

    private ScheduledFuture<?> sizeSetter;

    /**
     * @param scheduledExecutorService it's used to dealy interraction with
     *                                 configPairs to avoid spamming it with values
     *                                 while user still dragging element
     */
    public WindowGeometryPersisterImpl(Window window, String windowId, ConfigPairs configPairs, ScheduledExecutorService scheduledExecutorService) {
        Preconditions.checkArgument(scheduledExecutorService != null);
        Preconditions.checkArgument(window != null);
        Preconditions.checkArgument(StringUtils.hasText(windowId));
        Preconditions.checkArgument(configPairs != null);
        this.window = window;
        // version to version application windows sizes might change
        this.keyId = windowId + "_" + window.getMinimumSize();
        this.configPairs = configPairs;
        this.scheduledExecutorService = scheduledExecutorService;
        window.addComponentListener(this);
    }

    @Override
    public boolean restoreSize() {

        if (size == null) {
            return false;
        }
        window.setSize(size);
        return true;
    }

    // @Override
    // Preconditions.checkState(window != null, "instance was detached, can't do
    // much after that");
    @Override
    public void componentResized(ComponentEvent e) {
        if (window == null) {
            return;
        }
        Dimension size = window.getSize();
        if (size.getWidth() < MINIMUM_SANE_SIZE_VALUE || size.getHeight() < MINIMUM_SANE_SIZE_VALUE) {
            return;
        }
        if (sizeSetter != null) {
            sizeSetter.cancel(true);
        }
        sizeSetter = scheduledExecutorService.schedule(() -> configPairs.put(keySize(), window.getSize()), DELAY, DELAY_TIME_UNIT);
    }

    private String keySize() {
        return keyId + "_size";
    }

    // @Override
    // locationSetter = scheduledExecutorService
    @Override
    public boolean isAttached() {
        return window != null;
    }

    @Override
    public void detach() {
        Preconditions.checkState(window != null, "instance was detached, can't do much after that");
        window.removeComponentListener(this);
        window = null;
    }
}


if (size == null) {
            return false;
        }
        window.setSize(size);

/**
 * ****************************************************************************
 *  PGPTool is a desktop application for pgp encryption/decryption
 *
 * ****************************************************************************
 */
package org.pgptool.gui.ui.tools.geometrymemory;

import java.awt.Dimension;
import java.awt.Window;
import java.awt.event.ComponentAdapter;
import java.awt.event.ComponentEvent;
import java.util.concurrent.ScheduledExecutorService;
import java.util.concurrent.ScheduledFuture;
import java.util.concurrent.TimeUnit;
import org.pgptool.gui.configpairs.api.ConfigPairs;
import org.springframework.util.StringUtils;
import com.google.common.base.Preconditions;

public class WindowGeometryPersisterImpl extends ComponentAdapter implements WindowGeometryPersister {

    static final int MINIMUM_SANE_SIZE_VALUE = 10;

    private static final long DELAY = 500;

    private static final TimeUnit DELAY_TIME_UNIT = TimeUnit.MILLISECONDS;

    private ConfigPairs configPairs;

    private ScheduledExecutorService scheduledExecutorService;

    private Window window;

    private String keyId;

    private ScheduledFuture<?> sizeSetter;

    /**
     * @param scheduledExecutorService it's used to dealy interraction with
     *                                 configPairs to avoid spamming it with values
     *                                 while user still dragging element
     */
    public WindowGeometryPersisterImpl(Window window, String windowId, ConfigPairs configPairs, ScheduledExecutorService scheduledExecutorService) {
        Preconditions.checkArgument(scheduledExecutorService != null);
        Preconditions.checkArgument(window != null);
        Preconditions.checkArgument(StringUtils.hasText(windowId));
        Preconditions.checkArgument(configPairs != null);
        this.window = window;
        // version to version application windows sizes might change
        this.keyId = windowId + "_" + window.getMinimumSize();
        this.configPairs = configPairs;
        this.scheduledExecutorService = scheduledExecutorService;
        window.addComponentListener(this);
    }

    @Override
    public boolean restoreSize() {
        Preconditions.checkState(window != null, "instance was detached, can't do much after that");
        Dimension size = configPairs.find(keySize(), null);

        return true;
    }

    // @Override
    // Preconditions.checkState(window != null, "instance was detached, can't do
    // much after that");
    @Override
    public void componentResized(ComponentEvent e) {
        if (window == null) {
            return;
        }
        Dimension size = window.getSize();
        if (size.getWidth() < MINIMUM_SANE_SIZE_VALUE || size.getHeight() < MINIMUM_SANE_SIZE_VALUE) {
            return;
        }
        if (sizeSetter != null) {
            sizeSetter.cancel(true);
        }
        sizeSetter = scheduledExecutorService.schedule(() -> configPairs.put(keySize(), window.getSize()), DELAY, DELAY_TIME_UNIT);
    }

    private String keySize() {
        return keyId + "_size";
    }

    // @Override
    // locationSetter = scheduledExecutorService
    @Override
    public boolean isAttached() {
        return window != null;
    }

    @Override
    public void detach() {
        Preconditions.checkState(window != null, "instance was detached, can't do much after that");
        window.removeComponentListener(this);
        window = null;
    }
}


/**
 * ****************************************************************************
 *  PGPTool is a desktop application for pgp encryption/decryption
 *
 * ****************************************************************************
 */
package org.pgptool.gui.ui.tools.geometrymemory;

import java.awt.Dimension;
import java.awt.Window;
import java.awt.event.ComponentAdapter;
import java.awt.event.ComponentEvent;
import java.util.concurrent.ScheduledExecutorService;
import java.util.concurrent.ScheduledFuture;
import java.util.concurrent.TimeUnit;
import org.pgptool.gui.configpairs.api.ConfigPairs;
import org.springframework.util.StringUtils;
import com.google.common.base.Preconditions;

public class WindowGeometryPersisterImpl extends ComponentAdapter implements WindowGeometryPersister {

    static final int MINIMUM_SANE_SIZE_VALUE = 10;

    private static final long DELAY = 500;

    private static final TimeUnit DELAY_TIME_UNIT = TimeUnit.MILLISECONDS;

    private ConfigPairs configPairs;

    private ScheduledExecutorService scheduledExecutorService;

    private Window window;

    private String keyId;

    private ScheduledFuture<?> sizeSetter;

    /**
     * @param scheduledExecutorService it's used to dealy interraction with
     *                                 configPairs to avoid spamming it with values
     *                                 while user still dragging element
     */
    public WindowGeometryPersisterImpl(Window window, String windowId, ConfigPairs configPairs, ScheduledExecutorService scheduledExecutorService) {
        Preconditions.checkArgument(scheduledExecutorService != null);
        Preconditions.checkArgument(window != null);
        Preconditions.checkArgument(StringUtils.hasText(windowId));
        Preconditions.checkArgument(configPairs != null);
        this.window = window;
        // version to version application windows sizes might change
        this.keyId = windowId + "_" + window.getMinimumSize();
        this.configPairs = configPairs;
        this.scheduledExecutorService = scheduledExecutorService;
        window.addComponentListener(this);
    }

    @Override
    public boolean restoreSize() {
        Preconditions.checkState(window != null, "instance was detached, can't do much after that");
        Dimension size = configPairs.find(keySize(), null);
        if (size == null) {
            return false;
        }
        window.setSize(size);


/**
 * ****************************************************************************
 *  PGPTool is a desktop application for pgp encryption/decryption
 *
 * ****************************************************************************
 */
package org.pgptool.gui.ui.tools.geometrymemory;

import java.awt.Dimension;
import java.awt.Window;
import java.awt.event.ComponentAdapter;
import java.awt.event.ComponentEvent;
import java.util.concurrent.ScheduledExecutorService;
import java.util.concurrent.ScheduledFuture;
import java.util.concurrent.TimeUnit;
import org.pgptool.gui.configpairs.api.ConfigPairs;
import org.springframework.util.StringUtils;
import com.google.common.base.Preconditions;

public class WindowGeometryPersisterImpl extends ComponentAdapter implements WindowGeometryPersister {

    static final int MINIMUM_SANE_SIZE_VALUE = 10;

    private static final long DELAY = 500;

    private static final TimeUnit DELAY_TIME_UNIT = TimeUnit.MILLISECONDS;

    private ConfigPairs configPairs;

    private ScheduledExecutorService scheduledExecutorService;

    private Window window;

    private String keyId;

    private ScheduledFuture<?> sizeSetter;

    /**
     * @param scheduledExecutorService it's used to dealy interraction with
     *                                 configPairs to avoid spamming it with values
     *                                 while user still dragging element
     */
    public WindowGeometryPersisterImpl(Window window, String windowId, ConfigPairs configPairs, ScheduledExecutorService scheduledExecutorService) {
        Preconditions.checkArgument(scheduledExecutorService != null);
        Preconditions.checkArgument(window != null);
        Preconditions.checkArgument(StringUtils.hasText(windowId));
        Preconditions.checkArgument(configPairs != null);
        this.window = window;
        // version to version application windows sizes might change
        this.keyId = windowId + "_" + window.getMinimumSize();
        this.configPairs = configPairs;
        this.scheduledExecutorService = scheduledExecutorService;
        window.addComponentListener(this);
    }

    @Override
    public boolean restoreSize() {
        Preconditions.checkState(window != null, "instance was detached, can't do much after that");
        Dimension size = configPairs.find(keySize(), null);
        if (size == null) {
            return false;
        }
        window.setSize(size);
        return true;
    }

    // @Override
    // Preconditions.checkState(window != null, "instance was detached, can't do
    // much after that");
    @Override
    public void componentResized(ComponentEvent e) {
        if (window == null) {
            return;
        }
        Dimension size = window.getSize();
        if (size.getWidth() < MINIMUM_SANE_SIZE_VALUE || size.getHeight() < MINIMUM_SANE_SIZE_VALUE) {
            return;
        }
        if (sizeSetter != null) {
            sizeSetter.cancel(true);
        }
        sizeSetter = scheduledExecutorService.schedule(() -> configPairs.put(keySize(), window.getSize()), DELAY, DELAY_TIME_UNIT);
    }

    private String keySize() {
        return keyId + "_size";
    }

    // @Override
    // locationSetter = scheduledExecutorService
    @Override
    public boolean isAttached() {
        return window

    }

    @Override
    public void detach() {
        Preconditions.checkState(window != null, "instance was detached, can't do much after that");
        window.removeComponentListener(this);
        window = null;
    }
}


if (window == null) {
            return;
        }

/**
 * ****************************************************************************
 *  PGPTool is a desktop application for pgp encryption/decryption
 *
 * ****************************************************************************
 */
package org.pgptool.gui.ui.tools.geometrymemory;

import java.awt.Dimension;
import java.awt.Window;
import java.awt.event.ComponentAdapter;
import java.awt.event.ComponentEvent;
import java.util.concurrent.ScheduledExecutorService;
import java.util.concurrent.ScheduledFuture;
import java.util.concurrent.TimeUnit;
import org.pgptool.gui.configpairs.api.ConfigPairs;
import org.springframework.util.StringUtils;
import com.google.common.base.Preconditions;

public class WindowGeometryPersisterImpl extends ComponentAdapter implements WindowGeometryPersister {

    static final int MINIMUM_SANE_SIZE_VALUE = 10;

    private static final long DELAY = 500;

    private static final TimeUnit DELAY_TIME_UNIT = TimeUnit.MILLISECONDS;

    private ConfigPairs configPairs;

    private ScheduledExecutorService scheduledExecutorService;

    private Window window;

    private String keyId;

    private ScheduledFuture<?> sizeSetter;

    /**
     * @param scheduledExecutorService it's used to dealy interraction with
     *                                 configPairs to avoid spamming it with values
     *                                 while user still dragging element
     */
    public WindowGeometryPersisterImpl(Window window, String windowId, ConfigPairs configPairs, ScheduledExecutorService scheduledExecutorService) {
        Preconditions.checkArgument(scheduledExecutorService != null);
        Preconditions.checkArgument(window != null);
        Preconditions.checkArgument(StringUtils.hasText(windowId));
        Preconditions.checkArgument(configPairs != null);
        this.window = window;
        // version to version application windows sizes might change
        this.keyId = windowId + "_" + window.getMinimumSize();
        this.configPairs = configPairs;
        this.scheduledExecutorService = scheduledExecutorService;
        window.addComponentListener(this);
    }

    @Override
    public boolean restoreSize() {
        Preconditions.checkState(window != null, "instance was detached, can't do much after that");
        Dimension size = configPairs.find(keySize(), null);
        if (size == null) {
            return false;
        }
        window.setSize(size);
        return true;
    }

    // @Override
    // Preconditions.checkState(window != null, "instance was detached, can't do
    // much after that");
    @Override
    public void componentResized(ComponentEvent e) {

        Dimension size = window.getSize();
        if (size.getWidth() < MINIMUM_SANE_SIZE_VALUE || size.getHeight() < MINIMUM_SANE_SIZE_VALUE) {
            return;
        }
        if (sizeSetter != null) {
            sizeSetter.cancel(true);
        }
        sizeSetter = scheduledExecutorService.schedule(() -> configPairs.put(keySize(), window.getSize()), DELAY, DELAY_TIME_UNIT);
    }

    private String keySize() {
        return keyId + "_size";
    }

    // @Override
    // locationSetter = scheduledExecutorService
    @Override
    public boolean isAttached() {
        return window != null;
    }

    @Override
    public void detach() {
        Preconditions.checkState(window != null, "instance was detached, can't do much after that");
        window.removeComponentListener(this);
        window = null;
    }
}


/**
 * ****************************************************************************
 *  PGPTool is a desktop application for pgp encryption/decryption
 *
 * ****************************************************************************
 */
package org.pgptool.gui.ui.tools.geometrymemory;

import java.awt.Dimension;
import java.awt.Window;
import java.awt.event.ComponentAdapter;
import java.awt.event.ComponentEvent;
import java.util.concurrent.ScheduledExecutorService;
import java.util.concurrent.ScheduledFuture;
import java.util.concurrent.TimeUnit;
import org.pgptool.gui.configpairs.api.ConfigPairs;
import org.springframework.util.StringUtils;
import com.google.common.base.Preconditions;

public class WindowGeometryPersisterImpl extends ComponentAdapter implements WindowGeometryPersister {

    static final int MINIMUM_SANE_SIZE_VALUE = 10;

    private static final long DELAY = 500;

    private static final TimeUnit DELAY_TIME_UNIT = TimeUnit.MILLISECONDS;

    private ConfigPairs configPairs;

    private ScheduledExecutorService scheduledExecutorService;

    private Window window;

    private String keyId;

    private ScheduledFuture<?> sizeSetter;

    /**
     * @param scheduledExecutorService it's used to de

aly interraction with
     *                                 configPairs to avoid spamming it with values
     *                                 while user still dragging element
     */
    public WindowGeometryPersisterImpl(Window window, String windowId, ConfigPairs configPairs, ScheduledExecutorService scheduledExecutorService) {
        Preconditions.checkArgument(scheduledExecutorService != null);
        Preconditions.checkArgument(window != null);
        Preconditions.checkArgument(StringUtils.hasText(windowId));
        Preconditions.checkArgument(configPairs != null);
        this.window = window;
        // version to version application windows sizes might change
        this.keyId = windowId + "_" + window.getMinimumSize();
        this.configPairs = configPairs;
        this.scheduledExecutorService = scheduledExecutorService;
        window.addComponentListener(this);
    }

    @Override
    public boolean restoreSize() {
        Preconditions.checkState(window != null, "instance was detached, can't do much after that");
        Dimension size = configPairs.find(keySize(), null);
        if (size == null) {
            return false;
        }
        window.setSize(size);
        return true;
    }

    // @Override
    // Preconditions.checkState(window != null, "instance was detached, can't do
    // much after that");
    @Override
    public void componentResized(ComponentEvent e) {
        if (window == null) {
            return;
        }
        Dimension size = window.getSize();
        if (size.getWidth() < MINIMUM_SANE_SIZE_VALUE || size.getHeight() < MINIMUM_SANE_SIZE_VALUE) {
            return;
        }
        if (sizeSetter != null) {
            sizeSetter.cancel(true);
        }
        sizeSetter = scheduledExecutorService.schedule(() -> configPairs.put(keySize(), window.getSize()), DELAY, DELAY_TIME_UNIT);
    }

    private String keySize() {
        return keyId + "_size";
    }

    // @Override
    // locationSetter = scheduledExecutorService
    @Override
    public boolean isAttached() {
        return window != null;
    }

    @Override
    public void detach() {
        Preconditions.checkState(window != null, "instance was detached, can't do much after that");
        window.removeComponentListener(this);
        window = null;
    }
}


public void updateIndex(Client client, String indexName)

package de.komoot.photon.elasticsearch;

import org.elasticsearch.client.Client;
import org.elasticsearch.common.xcontent.XContentType;
import org.json.JSONObject;
import org.json.JSONTokener;
import java.io.InputStream;

/**
 * Encapsulates the ES index settings for the photon index. Adds functions to
 * manipulate and apply the settings.
 */
public class IndexSettings {

    private final JSONObject settings;

    /**
     * Create a new settings object and initialize it with the index settings
     * from the resources.
     */
    public IndexSettings() {
        final InputStream indexSettings = Thread.currentThread().getContextClassLoader().getResourceAsStream("index_settings.json");
        settings = new JSONObject(new JSONTokener(indexSettings));
    }

    /**
     * Set the number of shards to use for the index.
     *
     * @param numShards Number of shards to use.
     *
     * @return Return this object for chaining.
     */
    public IndexSettings setShards(Integer numShards) {
        if (numShards != null) {
            settings.put("index", new JSONObject().put("number_of_shards", numShards));
        }
        return this;
    }

    /**
     * Create a new index using the current index settings.
     *
     * @param client Client connection to use for creating the index.
     * @param indexName Name of the new index
     */
    public void createIndex(Client client, String indexName) {
        client.admin().indices().prepareCreate(indexName).setSettings(settings.toString(), XContentType.JSON).execute().actionGet();
    }

    /**
     * Update the index settings for an existing index.
     *
     * @param client Client connection to use for creating the index.
     * @param indexName Name of the index to update
     */

{
        client.admin().indices().prepareClose(PhotonIndex.NAME).execute().actionGet();
        client.admin().indices().prepareUpdateSettings(PhotonIndex.NAME).setSettings(settings.toString(), XContentType.JSON).execute().actionGet();
        client.admin().indices().prepareOpen(PhotonIndex.NAME).execute().actionGet();
    }
}


{
        client.admin().indices().prepareClose(PhotonIndex.NAME).execute().actionGet();
        client.admin().indices().prepareUpdateSettings(PhotonIndex.NAME).setSettings(settings.toString(), XContentType.JSON).execute().actionGet();
        client.admin().indices().prepareOpen(PhotonIndex.NAME).execute().actionGet();
    }

package de.komoot.photon.elasticsearch;

import org.elasticsearch.client.Client;
import org.elasticsearch.common.xcontent.XContentType;
import org.json.JSONObject;
import org.json.JSONTokener;
import java.io.InputStream;

/**
 * Encapsulates the ES index settings for the photon index. Adds functions to
 * manipulate and apply the settings.
 */
public class IndexSettings {

    private final JSONObject settings;

    /**
     * Create a new settings object and initialize it with the index settings
     * from the resources.
     */
    public IndexSettings() {
        final InputStream indexSettings = Thread.currentThread().getContextClassLoader().getResourceAsStream("index_settings.json");
        settings = new JSONObject(new JSONTokener(indexSettings));
    }

    /**
     * Set the number of shards to use for the index.
     *
     * @param numShards Number of shards to use.
     *
     * @return Return this object for chaining.
     */
    public IndexSettings setShards(Integer numShards) {
        if (numShards != null) {
            settings.put("index", new JSONObject().put("number_of_shards", numShards));
        }
        return this;
    }

    /**
     * Create a new index using the current index settings.
     *
     * @param client Client connection to use for creating the index.
     * @param indexName Name of the new index
     */
    public void createIndex(Client client, String indexName) {
        client.admin().indices().prepareCreate(indexName).setSettings(settings.toString(), XContentType.JSON).execute().actionGet();
    }

    /**
     * Update the index settings for an existing index.
     *
     * @param client Client connection to use for creating the index.
     * @param indexName Name of the index to update
     */
    public void updateIndex(Client client, String indexName)


package de.komoot.photon.elasticsearch;

import org.elasticsearch.client.Client;
import org.elasticsearch.common.xcontent.XContentType;
import org.json.JSONObject;
import org.json.JSONTokener;
import java.io.InputStream;

/**
 * Encapsulates the ES index settings for the photon index. Adds functions to
 * manipulate and apply the settings.
 */
public class IndexSettings {

    private final JSONObject settings;

    /**
     * Create a new settings object and initialize it with the index settings
     * from the resources.
     */
    public IndexSettings() {
        final InputStream indexSettings = Thread.currentThread().getContextClassLoader().getResourceAsStream("index_settings.json");
        settings = new JSONObject(new JSONTokener(indexSettings));
    }

    /**
     * Set the number of shards to use for the index.
     *
     * @param numShards Number of shards to use.
     *
     * @return Return this object for chaining.
     */
    public IndexSettings setShards(Integer numShards) {
        if (numShards != null) {
            settings.put("index", new JSONObject().put("number_of_shards", numShards));
        }
        return

    }

    /**
     * Create a new index using the current index settings.
     *
     * @param client Client connection to use for creating the index.
     * @param indexName Name of the new index
     */
    public void createIndex(Client client, String indexName) {
        client.admin().indices().prepareCreate(indexName).setSettings(settings.toString(), XContentType.JSON).execute().actionGet();
    }

    /**
     * Update the index settings for an existing index.
     *
     * @param client Client connection to use for creating the index.
     * @param indexName Name of the index to update
     */
    public void updateIndex(Client client, String indexName) {
        client.admin().indices().prepareClose(PhotonIndex.NAME).execute().actionGet();
        client.admin().indices().prepareUpdateSettings(PhotonIndex.NAME).setSettings(settings.toString(), XContentType.JSON).execute().actionGet();
        client.admin().indices().prepareOpen(PhotonIndex.NAME).execute().actionGet();
    }
}


public void createIndex(Client client, String indexName) {
        client.admin().indices().prepareCreate(indexName).setSettings(settings.toString(), XContentType.JSON).execute().actionGet();
    }

package de.komoot.photon.elasticsearch;

import org.elasticsearch.client.Client;
import org.elasticsearch.common.xcontent.XContentType;
import org.json.JSONObject;
import org.json.JSONTokener;
import java.io.InputStream;

/**
 * Encapsulates the ES index settings for the photon index. Adds functions to
 * manipulate and apply the settings.
 */
public class IndexSettings {

    private final JSONObject settings;

    /**
     * Create a new settings object and initialize it with the index settings
     * from the resources.
     */
    public IndexSettings() {
        final InputStream indexSettings = Thread.currentThread().getContextClassLoader().getResourceAsStream("index_settings.json");
        settings = new JSONObject(new JSONTokener(indexSettings));
    }

    /**
     * Set the number of shards to use for the index.
     *
     * @param numShards Number of shards to use.
     *
     * @return Return this object for chaining.
     */
    public IndexSettings setShards(Integer numShards) {
        if (numShards != null) {
            settings.put("index", new JSONObject().put("number_of_shards", numShards));
        }
        return this;
    }

    /**
     * Create a new index using the current index settings.
     *
     * @param client Client connection to use for creating the index.
     * @param indexName Name of the new index
     */

    /**
     * Update the index settings for an existing index.
     *
     * @param client Client connection to use for creating the index.
     * @param indexName Name of the index to update
     */
    public void updateIndex(Client client, String indexName) {
        client.admin().indices().prepareClose(PhotonIndex.NAME).execute().actionGet();
        client.admin().indices().prepareUpdateSettings(PhotonIndex.NAME).setSettings(settings.toString(), XContentType.JSON).execute().actionGet();
        client.admin().indices().prepareOpen(PhotonIndex.NAME).execute().actionGet();
    }
}


if (numShards != null) {
            settings.put("index", new JSONObject().put("number_of_shards", numShards));
        }

package de.komoot.photon.elasticsearch;

import org.elasticsearch.client.Client;
import org.elasticsearch.common.xcontent.XContentType;
import org.json.JSONObject;
import org.json.JSONTokener;
import java.io.InputStream;

/**
 * Encapsulates the ES index settings for the photon index. Adds functions to
 * manipulate and apply the settings.
 */
public class IndexSettings {

    private final JSONObject settings;

    /**
     * Create a new settings object and initialize it with the index settings
     * from the resources.
     */
    public IndexSettings() {
        final InputStream indexSettings = Thread.currentThread().getContextClassLoader().getResourceAsStream("index_settings.json");
        settings = new JSONObject(new JSONTokener(indexSettings));
    }

    /**
     * Set the number of shards to use for the index.
     *
     * @param numShards Number of shards to use.
     *
     * @return Return this object for chaining.
     */
    public IndexSettings setShards(Integer numShards) {

        return this;
    }

    /**
     * Create a new index using the current index settings.
     *
     * @param client Client connection to use for creating the index.
     * @param indexName Name of the new index
     */
    public void createIndex(Client client, String indexName) {
        client.admin().indices().prepareCreate(indexName).setSettings(settings.toString(), XContentType.JSON).execute().actionGet();
    }

    /**
     * Update the index settings for an existing index.
     *
     * @param client Client connection to use for creating the index.
     * @param indexName Name of the index to update
     */
    public void updateIndex(Client client, String indexName) {
        client.admin().indices().prepareClose(PhotonIndex.NAME).execute().actionGet();
        client.admin().indices().prepareUpdateSettings(PhotonIndex.NAME).setSettings(settings.toString(), XContentType.JSON).execute().actionGet();
        client.admin().indices().prepareOpen(PhotonIndex.NAME).execute().actionGet();
    }
}


package de.komoot.photon.elasticsearch;

import org.elasticsearch.client.Client;
import org.elasticsearch.common.xcontent.XContentType;
import org.json.JSONObject;
import org.json.JSONTokener;
import java.io.InputStream;

/**
 * Encapsulates the ES index settings for the photon index. Adds functions to
 * manipulate and apply the settings.
 */
public class IndexSettings {

    private final JSONObject settings;

    /**
     * Create a new settings object and initialize it with the index settings
     * from the resources.
     */
    public IndexSettings() {
        final InputStream indexSettings = Thread.currentThread().getContextClassLoader().getResourceAsStream("index_settings.json");
        settings = new JSONObject(new JSONTokener(indexSettings));
    }

    /**
     * Set the number of shards to use for the index.
     *
     * @param numShards Number of shards to use.
     *
     * @return Return this object for chaining.
     */
    public IndexSettings setShards(Integer numShards) {
        if (numShards != null) {
            settings.put("index", new JSONObject().put("number_of_shards", numShards));
        }
        return this;
    }

    /**
     * Create a new index using the current index settings.
     *
     * @param client Client connection to use for creat

ing the index.
     * @param indexName Name of the new index
     */
    public void createIndex(Client client, String indexName) {
        client.admin().indices().prepareCreate(indexName).setSettings(settings.toString(), XContentType.JSON).execute().actionGet();
    }

    /**
     * Update the index settings for an existing index.
     *
     * @param client Client connection to use for creating the index.
     * @param indexName Name of the index to update
     */
    public void updateIndex(Client client, String indexName) {
        client.admin().indices().prepareClose(PhotonIndex.NAME).execute().actionGet();
        client.admin().indices().prepareUpdateSettings(PhotonIndex.NAME).setSettings(settings.toString(), XContentType.JSON).execute().actionGet();
        client.admin().indices().prepareOpen(PhotonIndex.NAME).execute().actionGet();
    }
}


@Override
    public void getCategoryNovelsError(String errorMsg)

package com.feng.freader.presenter;

import com.feng.freader.base.BasePresenter;
import com.feng.freader.constract.IPressContract;
import com.feng.freader.entity.data.DiscoveryNovelData;
import com.feng.freader.model.PressModel;
import java.util.List;

/**
 * @author Feng Zhaohao
 * Created on 2019/12/22
 */
public class PressPresenter extends BasePresenter<IPressContract.View> implements IPressContract.Presenter {

    private IPressContract.Model mModel;

    public PressPresenter() {
        mModel = new PressModel(this);
    }

    @Override
    public void getCategoryNovelsSuccess(List<DiscoveryNovelData> dataList) {
        if (isAttachView()) {
            getMvpView().getCategoryNovelsSuccess(dataList);
        }
    }

{
        if (isAttachView()) {
            getMvpView().getCategoryNovelsError(errorMsg);
        }
    }

    @Override
    public void getCategoryNovels() {
        mModel.getCategoryNovels();
    }
}


{
        if (isAttachView()) {
            getMvpView().getCategoryNovelsError(errorMsg);
        }
    }

package com.feng.freader.presenter;

import com.feng.freader.base.BasePresenter;
import com.feng.freader.constract.IPressContract;
import com.feng.freader.entity.data.DiscoveryNovelData;
import com.feng.freader.model.PressModel;
import java.util.List;

/**
 * @author Feng Zhaohao
 * Created on 2019/12/22
 */
public class PressPresenter extends BasePresenter<IPressContract.View> implements IPressContract.Presenter {

    private IPressContract.Model mModel;

    public PressPresenter() {
        mModel = new PressModel(this);
    }

    @Override
    public void getCategoryNovelsSuccess(List<DiscoveryNovelData> dataList) {
        if (isAttachView()) {
            getMvpView().getCategoryNovelsSuccess(dataList);
        }
    }

    @Override
    public void getCategoryNovelsError(String errorMsg)

    @Override
    public void getCategoryNovels() {
        mModel.getCategoryNovels();
    }
}


package com.feng.freader.presenter;

import com.feng.freader.base.BasePresenter;
import com.feng.freader.constract.IPressContract;
import com.feng.freader.entity.data.DiscoveryNovelData;
import com.feng.freader.model.PressModel;
import java.util.List;

/**
 * @author Feng Zhaohao
 * Created on 2019/12/22
 */
public class PressPresenter extends BasePresenter<IPressContract.View> implements IPressContract.Presenter {

    private IPressContract.Model mModel;

    public PressPresenter() {
        mModel = new PressModel(this);
    }

    @Override
    public void getCategoryNovelsSuccess(List<DiscoveryNovelData> dataList) {
        if (isAttachView()) {
            getMvpView().getCategoryNovelsSuccess(dataList);
        }
    }

    @Override
    public void getCategoryNovelsError(String errorMsg) {
        if (isAttachView()) {
            getMvpView().getCategoryNovelsError(errorMsg);
        }
    }

    @Override
    public void getCategoryNovels() {
        mModel


if (isAttachView()) {
            getMvpView().getCategoryNovelsError(errorMsg);
        }

package com.feng.freader.presenter;

import com.feng.freader.base.BasePresenter;
import com.feng.freader.constract.IPressContract;
import com.feng.freader.entity.data.DiscoveryNovelData;
import com.feng.freader.model.PressModel;
import java.util.List;

/**
 * @author Feng Zhaohao
 * Created on 2019/12/22
 */
public class PressPresenter extends BasePresenter<IPressContract.View> implements IPressContract.Presenter {

    private IPressContract.Model mModel;

    public PressPresenter() {
        mModel = new PressModel(this);
    }

    @Override
    public void getCategoryNovelsSuccess(List<DiscoveryNovelData> dataList) {
        if (isAttachView()) {
            getMvpView().getCategoryNovelsSuccess(dataList);
        }
    }

    @Override
    public void getCategoryNovelsError(String errorMsg) {

    }

    @Override
    public void getCategoryNovels() {
        mModel.getCategoryNovels();
    }
}


package com.feng.freader.presenter;

import com.feng.freader.base.BasePresenter;
import com.feng.freader.constract.IPressContract;
import com.feng.freader.entity.data.DiscoveryNovelData;
import com.feng.freader.model.PressModel;
import java.util.List;

/**
 * @author Feng Zhaohao
 * Created on 2019/12/22
 */
public class PressPresenter extends BasePresenter<IPressContract.View> implements IPressContract.Presenter {

    private IPressContract.Model mModel;

    public PressPresenter() {
        mModel = new PressModel(this);
    }

    @Override
    public void getCategoryNovelsSuccess(List<DiscoveryNovelData> dataList) {
        if (isAttach

View()) {
            getMvpView().getCategoryNovelsSuccess(dataList);
        }
    }

    @Override
    public void getCategoryNovelsError(String errorMsg) {
        if (isAttachView()) {
            getMvpView().getCategoryNovelsError(errorMsg);
        }
    }

    @Override
    public void getCategoryNovels() {
        mModel.getCategoryNovels();
    }
}


// For information as to what this class does, see the Javadoc, below.       //
package edu.cmu.tetrad.bayes;

import edu.cmu.tetrad.graph.Node;
import java.util.List;

/**
 * Interface containing methods to calculate probabilities for systems of
 * discrete variables. See instantiations for more details.
 *
 * @author Joseph Ramsey
 */
interface DiscreteProbs {

    /**
     * @return the variables over which probabilities and conditional
     * probabilities will be calculated.
     */
    List<Node> getVariables();

    /**
     * @return the probability in a given cell.
     */

/**
     * Calculates the probability P(a), where a is a two dimensional boolean
     * array, with a[i] representing particular combinations of values for
     * bayesIm.getNode(i) as boolean arrays of length bayesim.getNumSplits(i),
     * with a[i][j] = true iff the condition includes the j'th value of
     * bayesIm.getNode(i).
     */
    double getProb(Proposition assertion);

    /**
     *
Calculates the conditional probability P(a|b), where a and b are two
     * dimensional boolean arrays, with a[i] and b[i] representing particular
     * combinations of values for bayesIm.getNode(i) as boolean arrays of length
     * bayesim.getNumSplits(i), with a[i][j] = true iff the condition
     * includes the j'th value of bayesIm.getNode(i), and similarly for
     * b[i][j].

This does not allow all possible conditional
     * probabilities to be calculated, since for instance the condition X1 = 1
     * or X2 = 2 cannot be represented this way directly, but it does capture
     * all conditions for which this type of method would generally be of
     * interest.

     */
    double getConditionalProb(Proposition assertion, Proposition condition);

    /**
     * @return true if a missing values case was encountered during the last
     * method call.
     */
    boolean isMissingValueCaseFound();
}


// For information as to what this class does, see the Javadoc, below.       //
package edu.cmu.tetrad.bayes;

import edu.cmu.tetrad.graph.Node;
import java.util.List;

/**
 * Interface containing methods to calculate probabilities for systems of
 * discrete variables. See instantiations for more details.
 *
 * @author Joseph Ramsey
 */
interface DiscreteProbs {

    /**
     * @return the variables over which probabilities and conditional
     * probabilities will be calculated.
     */
    List<Node> getVariables();

    /**
     * @return the probability in a given cell.
     */
    double getCellProb(int[] cell);

    /**
     * Calculates the probability P(a), where a is a two dimensional boolean
     * array, with a[i] representing particular combinations of values for

* bayesIm.getNode(i) as boolean arrays of length bayesim.getNumSplits(i),
     * with a[i][j] = true iff the condition includes the j'th value of
     * bayesIm.getNode(i).
     */
    double getProb(Proposition assertion);

    /**
     *
Calculates the conditional probability P(a|b), where a and b are two
     * dimensional boolean arrays, with a[i] and b[i] representing particular
     * combinations of values for bayesIm.getNode(i) as boolean arrays of length
     * bayesim.getNumSplits(i), with a[i][j] = true iff the condition
     * includes the j'th value of bayesIm.getNode(i), and similarly for
     * b[i][j].

This does not allow all possible conditional
     * probabilities to be calculated, since for instance the condition X1 = 1
     * or X2 = 2 cannot be represented this way directly, but it does capture
     * all conditions for which this type of method would generally be of
     * interest.

     */
    double getConditionalProb(Proposition assertion, Proposition condition);

    /**
     * @return true if a missing values case was encountered during the last
     * method call.
     */
    boolean isMissingValueCaseFound();
}


private static double windowedSinc(double x, double y)

/*
 * Preprocess frames
 *
 * All rights reserved. Contact Nayuki for licensing.
 * http://example.com
 */
import java.awt.image.BufferedImage;
import java.io.File;
import java.io.IOException;
import java.util.Arrays;
import java.util.concurrent.BrokenBarrierException;
import java.util.concurrent.CyclicBarrier;
import java.util.concurrent.atomic.AtomicInteger;
import javax.imageio.ImageIO;

public final class PreprocessFrames {

    // Configuration
    private static final int INPUT_FRAME_START = 990;

    private static final int INPUT_FRAME_END = 3900;

    private static final int INPUT_FRAME_DECIMATE = 3;

    private static final int IMAGE_WIDTH = 1280;

    private static final int IMAGE_HEIGHT = 192;

    private static final int IMAGE_UPSCALE = 4;

    public static void main(String[] args) throws IOException {
        // Get and check arguments
        if (args.length != 2) {
            System.err.println("Usage: java PreprocessFrames InDir OutDir");
            System.exit(1);
            return;
        }
        File inDir = new File(args[0]);
        File outDir = new File(args[1]);
        if (!inDir.isDirectory())
            throw new RuntimeException("Invalid input directory");
        if (!outDir.isDirectory())
            throw new RuntimeException("Invalid output directory");
        // Process image frames
        for (int inFrame = INPUT_FRAME_START, outFrame = 0; inFrame < INPUT_FRAME_END; inFrame += INPUT_FRAME_DECIMATE, outFrame++) {
            File inFile = new File(inDir, String.format("%04d.bmp", inFrame));
            File outFile = new File(outDir, String.format("%04d.bmp", outFrame));
            FastSincImageResampler.resampleFile(inFile, IMAGE_WIDTH * IMAGE_UPSCALE, IMAGE_HEIGHT * IMAGE_UPSCALE, outFile);
            System.out.printf("%s --> %s%n", inFile.getName(), outFile.getName());
        }
    }

    // Based on http://example.com
    private static final class FastSincImageResampler {

        /* Convenience methods */
        /**
         * Resamples the specified image file to the specified output dimensions, writing to the specified output file.
         * @param inFile the input image file (must be in BMP or PNG format)
         * @param outWidth the output image width (must be positive)
         * @param outHeight the output image height (must be positive)
         * @param outFile the output image file
         * @throws IOException if an I/O exception occurred in reading the input image file or writing the output image file
         */
        public static void resampleFile(File inFile, int outWidth, int outHeight, File outFile) throws IOException {
            FastSincImageResampler rs = new FastSincImageResampler();
            rs.inputFile = inFile;
            rs.outputWidth = outWidth;
            rs.outputHeight = outHeight;
            rs.outputFile = outFile;
            rs.run();
        }

        /* Full functionality */
        public File inputFile = null;

        public BufferedImage inputImage = null;

        // run() does not read this, but will overwrite this field
        public int inputWidth = -1;

        // run() does not read this, but will overwrite this field
        public int inputHeight = -1;

        // Must set to a positive number
        public int outputWidth = -1;

        // Must set to a positive number
        public int outputHeight = -1;

        // Horizontal filter length (set a positive value, otherwise an automatic default value will be computed)
        public double horizontalFilterLength = -1;

        // Vertical   filter length (set a positive value, otherwise an automatic default value will be computed)
        public double verticalFilterLength = -1;

        // Number of threads to use (set a positive value, otherwise an automatic default value will be computed)
        public int threads = -1;

        // run() does not read this, but will overwrite this field
        public BufferedImage outputImage = null;

        public File outputFile = null;

        // Must be "png", "bmp", or null (auto-detection based on outputFile's extension)
        public String outputFileType = null;

        /**
         * Constructs a blank resampler object - certain fields must be set before calling {@code run()}.
         */
        public FastSincImageResampler() {
        }

        /**
         * Runs the resampler. The data flow operates on a "waterfall" model, reading and writing the fields from top to bottom:
         *

         *
If {@code inputFile} is non-{@code null}, then it is read into {@code inputImage}.

         *
{@code inputImage} needs to be non-{@code null} now (set explicitly or read from {@code inputFile}).

         *
{@code inputImage} is read into {@code inputWidth} and {@code inputHeight}.

         *
{@code outputWidth} and {@code outputHeight} must be positive (set explicitly).

         *
If a filter length is zero or negative, then a default filter length is computed and set for that axis.

         *
If {@code threads} is zero or negative, then a default number of threads is computed and set.

         *
{@code outputImage} is computed by resampling {@code inputImage}. (This is the main purpose of the class.)

         *
If {@code outputFile} is non-{@code null} and {@code outputFileType} is {@code null}: The type is set to {@code "png"} or {@code "bmp"}
         *       if the output file has that extension (case-insensitive), otherwise it is set to {@code "png"} by default.

         *
If {@code outputFile} is non-{@code null}, then {@code outputImage} is written to the file.

         *

         *
After calling {@code run()}, it is recommend that this object should not be reused for another resampling operation.
         * This is because various fields probably need to be cleared, such as the filter length and output file type.

         * @throws IOException if an I/O exception occurred
         * @throws IllegalStateException if there is no input image or the output dimensions are not set to positive values
         */
        public void run() throws IOException {
            // Read input file (optional)
            if (inputFile != null) {
                try {
                    inputImage = ImageIO.read(inputFile);
                } catch (IOException e) {
                    throw new IOException("Error reading input file (" + inputFile + "): " + e.getMessage(), e);
                }
            }
            // Get input image dimensions
            if (inputImage == null)
                throw new IllegalStateException("No input image");
            inputWidth = inputImage.getWidth();
            inputHeight = inputImage.getHeight();
            // Calculate filter lengths (optional)
            if (outputWidth <= 0 || outputHeight <= 0)
                throw new IllegalStateException("Output dimensions not set");
            if (horizontalFilterLength <= 0)
                horizontalFilterLength = Math.max((double) inputWidth / outputWidth, 1) * 4.0;
            if (verticalFilterLength <= 0)
                verticalFilterLength = Math.max((double) inputHeight / outputHeight, 1) * 4.0;
            // Resample the image
            if (threads <= 0)
                threads = Runtime.getRuntime().availableProcessors();
            resampleImage();
            // Write output file (optional)
            if (outputFile != null) {
                if (outputFileType == null) {
                    // Auto-detection by file extension
                    String lowername = outputFile.getName().toLowerCase();
                    if (lowername.endsWith(".bmp"))
                        outputFileType = "bmp";
                    else
                        // Default
                        outputFileType = "png";
                }
                try {
                    ImageIO.write(outputImage, outputFileType, outputFile);
                } catch (IOException e) {
                    throw new IOException("Error writing output file (" + outputFile + "): " + e.getMessage(), e);
                }
            }
        }

        private void resampleImage() {
            final int inWidth = inputWidth;
            final int inHeight = inputHeight;
            final int outWidth = outputWidth;
            final int outHeight = outputHeight;
            // Get packed int pixels
            int[] inPixels = new int[inWidth * inHeight];
            inputImage.getRGB(0, 0, inWidth, inHeight, inPixels, 0, inWidth);
            // Convert to float
            final float[] inVert = new float[inWidth * inHeight * 3];
            for (int i = 0, j = 0; i < inPixels.length; i++, j += 3) {
                int rgb = inPixels[i];
                inVert[j + 0] = (float) Math.pow(((rgb >>> 16) & 0xFF) / 255.0, 2.2);
                inVert[j + 1] = (float) Math.pow(((rgb >>> 8) & 0xFF) / 255.0, 2.2);
                inVert[j + 2] = (float) Math.pow(((rgb >>> 0) & 0xFF) / 255.0, 2.2);
            }
            inPixels = null;
            Thread[] thr = new Thread[threads];
            final AtomicInteger sharedY = new AtomicInteger(0);
            final float[] inHorz = new float[inWidth * outHeight * 3];
            final CyclicBarrier barrier = new CyclicBarrier(threads);
            final AtomicInteger sharedX = new AtomicInteger(0);
            final int[] outPixels = new int[outWidth * outHeight];
            for (int i = 0; i < thr.length; i++) {
                thr[i] = new Thread() {

                    public void run() {
                        // Resample vertically and transpose
                        {
                            double sincScale = Math.min((double) outHeight / inHeight, 1);
                            double[] weights = new double[(int) verticalFilterLength + 1];
                            float[] outVertRow = new float[inWidth * 3];
                            while (true) {
                                // For each output row
                                int y = sharedY.getAndIncrement();
                                if (y >= outHeight)
                                    break;
                                double weightSum = 0;
                                // In input image coordinates
                                double centerY = (y + 0.5) / outHeight * inHeight;
                                double filterStartY = centerY - verticalFilterLength / 2;
                                int startIndex = (int) Math.ceil(filterStartY - 0.5);
                                for (int i = 0; i < weights.length; i++) {
                                    int inputY = startIndex + i;
                                    double weight = windowedSinc((inputY + 0.5 - centerY) * sincScale, (inputY + 0.5 - filterStartY) / verticalFilterLength);
                                    weights[i] = weight;
                                    weightSum += weight;
                                }
                                Arrays.fill(outVertRow, 0);
                                for (int i = 0; i < weights.length; i++) {
                                    double weight = weights[i] / weightSum;
                                    int clippedInputY = Math.min(Math.max(startIndex + i, 0), inHeight - 1);
                                    for (int x = 0; x < inWidth; x++) {
                                        // For each pixel in the row
                                        int j = (clippedInputY * inWidth + x) * 3;
                                        outVertRow[x * 3 + 0] += inVert[j + 0] * weight;
                                        outVertRow[x * 3 + 1] += inVert[j + 1] * weight;
                                        outVertRow[x * 3 + 2] += inVert[j + 2] * weight;
                                    }
                                }
                                for (int x = 0; x < inWidth; x++) {
                                    int j = (x * outHeight + y) * 3;
                                    inHorz[j + 0] = outVertRow[x * 3 + 0];
                                    inHorz[j + 1] = outVertRow[x * 3 + 1];
                                    inHorz[j + 2] = outVertRow[x * 3 + 2];
                                }
                            }
                        }
                        // Wait for all threads to finish the phase
                        try {
                            barrier.await();
                        } catch (InterruptedException e) {
                            throw new RuntimeException(e);
                        } catch (BrokenBarrierException e) {
                            throw new RuntimeException(e);
                        }
                        // Resample horizontally and transpose
                        {
                            double sincScale = Math.min((double) outWidth / inWidth, 1);
                            double[] weights = new double[(int) horizontalFilterLength + 1];
                            double[] outHorzCol = new double[outHeight * 3];
                            while (true) {
                                // For each output column
                                int x = sharedX.getAndIncrement();
                                if (x >= outWidth)
                                    break;
                                double weightSum = 0;
                                // In input image coordinates
                                double centerX = (x + 0.5) / outWidth * inWidth;
                                double filterStartX = centerX - horizontalFilterLength / 2;
                                int startIndex = (int) Math.ceil(filterStartX - 0.5);
                                for (int i = 0; i < weights.length; i++) {
                                    int inputX = startIndex + i;
                                    double weight = windowedSinc((inputX + 0.5 - centerX) * sincScale, (inputX + 0.5 - filterStartX) / horizontalFilterLength);
                                    weights[i] = weight;
                                    weightSum += weight;
                                }
                                Arrays.fill(outHorzCol, 0);
                                for (int i = 0; i < weights.length; i++) {
                                    double weight = weights[i] / weightSum;
                                    int clippedInputX = Math.min(Math.max(startIndex + i, 0), inWidth - 1);
                                    for (int y = 0; y < outHeight; y++) {
                                        // For each pixel in the column
                                        int j = (clippedInputX * outHeight + y) * 3;
                                        outHorzCol[y * 3 + 0] += inHorz[j + 0] * weight;
                                        outHorzCol[y * 3 + 1] += inHorz[j + 1] * weight;
                                        outHorzCol[y * 3 + 2] += inHorz[j + 2] * weight;
                                    }
                                }
                                for (int y = 0; y < outHeight; y++) {
                                    // Convert to 8 bits per channel and pack integers
                                    double r = outHorzCol[y * 3 + 0] * 255;
                                    if (r < 0)
                                        r = 0;
                                    if (r > 255)
                                        r = 255;
                                    double g = outHorzCol[y * 3 + 1] * 255;
                                    if (g < 0)
                                        g = 0;
                                    if (g > 255)
                                        g = 255;
                                    double b = outHorzCol[y * 3 + 2] * 255;
                                    if (b < 0)
                                        b = 0;
                                    if (b > 255)
                                        b = 255;
                                    outPixels[y * outWidth + x] = (int) (r + 0.5) << 16 | (int) (g + 0.5) << 8 | (int) (b + 0.5);
                                }
                            }
                        }
                    }
                };
                thr[i].start();
            }
            try {
                for (Thread th : thr) th.join();
            } catch (InterruptedException e) {
                throw new RuntimeException(e);
            }
            outputImage = new BufferedImage(outWidth, outHeight, BufferedImage.TYPE_INT_RGB);
            outputImage.setRGB(0, 0, outWidth, outHeight, outPixels, 0, outWidth);
        }

        // x is measured in half-cycles; y is for the window which has the domain [0, 1]

{
            x *= Math.PI;
            double sinc = x != 0 ? Math.sin(x) / x : 1;
            // Triangle window
            double window = 0 <= y && y <= 1 ? 1 - Math.abs(y - 0.5) * 2 : 0;
            return sinc * window;
        }
    }
}


{
            FastSincImageResampler rs = new FastSincImageResampler();
            rs.inputFile = inFile;
            rs.outputWidth = outWidth;
            rs.outputHeight = outHeight;
            rs.outputFile = outFile;
            rs.run();
        }

/*
 * Preprocess frames
 *
 * All rights reserved. Contact Nayuki for licensing.
 * http://example.com
 */
import java.awt.image.BufferedImage;
import java.io.File;
import java.io.IOException;
import java.util.Arrays;
import java.util.concurrent.BrokenBarrierException;
import java.util.concurrent.CyclicBarrier;
import java.util.concurrent.atomic.AtomicInteger;
import javax.imageio.ImageIO;

public final class PreprocessFrames {

    // Configuration
    private static final int INPUT_FRAME_START = 990;

    private static final int INPUT_FRAME_END = 3900;

    private static final int INPUT_FRAME_DECIMATE = 3;

    private static final int IMAGE_WIDTH = 1280;

    private static final int IMAGE_HEIGHT = 192;

    private static final int IMAGE_UPSCALE = 4;

    public static void main(String[] args) throws IOException {
        // Get and check arguments
        if (args.length != 2) {
            System.err.println("Usage: java PreprocessFrames InDir OutDir");
            System.exit(1);
            return;
        }
        File inDir = new File(args[0]);
        File outDir = new File(args[1]);
        if (!inDir.isDirectory())
            throw new RuntimeException("Invalid input directory");
        if (!outDir.isDirectory())
            throw new RuntimeException("Invalid output directory");
        // Process image frames
        for (int inFrame = INPUT_FRAME_START, outFrame = 0; inFrame < INPUT_FRAME_END; inFrame += INPUT_FRAME_DECIMATE, outFrame++) {
            File inFile = new File(inDir, String.format("%04d.bmp", inFrame));
            File outFile = new File(outDir, String.format("%04d.bmp", outFrame));
            FastSincImageResampler.resampleFile(inFile, IMAGE_WIDTH * IMAGE_UPSCALE, IMAGE_HEIGHT * IMAGE_UPSCALE, outFile);
            System.out.printf("%s --> %s%n", inFile.getName(), outFile.getName());
        }
    }

    // Based on http://example.com
    private static final class FastSincImageResampler {

        /* Convenience methods */
        /**
         * Resamples the specified image file to the specified output dimensions, writing to the specified output file.
         * @param inFile the input image file (must be in BMP or PNG format)
         * @param outWidth the output image width (must be positive)
         * @param outHeight the output image height (must be positive)
         * @param outFile the output image file
         * @throws IOException if an I/O exception occurred in reading the input image file or writing the output image file
         */
        public static void resampleFile(File inFile, int outWidth, int outHeight, File outFile) throws IOException

/* Full functionality */
        public File inputFile = null;

        public BufferedImage inputImage = null;

        // run() does not read this, but will overwrite this field
        public int inputWidth = -1;

        // run() does not read this, but will overwrite this field
        public int inputHeight = -1;

        // Must set to a positive number
        public int outputWidth = -1;

        // Must set to a positive number
        public int outputHeight = -1;

        // Horizontal filter length (set a positive value, otherwise an automatic default value will be computed)
        public double horizontalFilterLength = -1;

        // Vertical   filter length (set a positive value, otherwise an automatic default value will be computed)
        public double verticalFilterLength = -1;

        // Number of threads to use (set a positive value, otherwise an automatic default value will be computed)
        public int threads = -1;

        // run() does not read this, but will overwrite this field
        public BufferedImage outputImage = null;

        public File outputFile = null;

        // Must be "png", "bmp", or null (auto-detection based on outputFile's extension)
        public String outputFileType = null;

        /**
         * Constructs a blank resampler object - certain fields must be set before calling {@code run()}.
         */
        public FastSincImageResampler() {
        }

        /**
         * Runs the resampler. The data flow operates on a "waterfall" model, reading and writing the fields from top to bottom:
         *

         *
If {@code inputFile} is non-{@code null}, then it is read into {@code inputImage}.

         *
{@code inputImage} needs to be non-{@code null} now (set explicitly or read from {@code inputFile}).

         *
{@code inputImage} is read into {@code inputWidth} and {@code inputHeight}.

         *
{@code outputWidth} and {@code outputHeight} must be positive (set explicitly).

         *
If a filter length is zero or negative, then a default filter length is computed and set for that axis.

         *
If {@code threads} is zero or negative, then a default number of threads is computed and set.

         *
{@code outputImage} is computed by resampling {@code inputImage}. (This is the main purpose of the class.)

         *
If {@code outputFile} is non-{@code null} and {@code outputFileType} is {@code null}: The type is set to {@code "png"} or {@code "bmp"}
         *       if the output file has that extension (case-insensitive), otherwise it is set to {@code "png"} by default.

         *
If {@code outputFile} is non-{@code null}, then {@code outputImage} is written to the file.

         *

         *
After calling {@code run()}, it is recommend that this object should not be reused for another resampling operation.
         * This is because various fields probably need to be cleared, such as the filter length and output file type.

         * @throws IOException if an I/O exception occurred
         * @throws IllegalStateException if there is no input image or the output dimensions are not set to positive values
         */
        public void run() throws IOException {
            // Read input file (optional)
            if (inputFile != null) {
                try {
                    inputImage = ImageIO.read(inputFile);
                } catch (IOException e) {
                    throw new IOException("Error reading input file (" + inputFile + "): " + e.getMessage(), e);
                }
            }
            // Get input image dimensions
            if (inputImage == null)
                throw new IllegalStateException("No input image");
            inputWidth = inputImage.getWidth();
            inputHeight = inputImage.getHeight();
            // Calculate filter lengths (optional)
            if (outputWidth <= 0 || outputHeight <= 0)
                throw new IllegalStateException("Output dimensions not set");
            if (horizontalFilterLength <= 0)
                horizontalFilterLength = Math.max((double) inputWidth / outputWidth, 1) * 4.0;
            if (verticalFilterLength <= 0)
                verticalFilterLength = Math.max((double) inputHeight / outputHeight, 1) * 4.0;
            // Resample the image
            if (threads <= 0)
                threads = Runtime.getRuntime().availableProcessors();
            resampleImage();
            // Write output file (optional)
            if (outputFile != null) {
                if (outputFileType == null) {
                    // Auto-detection by file extension
                    String lowername = outputFile.getName().toLowerCase();
                    if (lowername.endsWith(".bmp"))
                        outputFileType = "bmp";
                    else
                        // Default
                        outputFileType = "png";
                }
                try {
                    ImageIO.write(outputImage, outputFileType, outputFile);
                } catch (IOException e) {
                    throw new IOException("Error writing output file (" + outputFile + "): " + e.getMessage(), e);
                }
            }
        }

        private void resampleImage() {
            final int inWidth = inputWidth;
            final int inHeight = inputHeight;
            final int outWidth = outputWidth;
            final int outHeight = outputHeight;
            // Get packed int pixels
            int[] inPixels = new int[inWidth * inHeight];
            inputImage.getRGB(0, 0, inWidth, inHeight, inPixels, 0, inWidth);
            // Convert to float
            final float[] inVert = new float[inWidth * inHeight * 3];
            for (int i = 0, j = 0; i < inPixels.length; i++, j += 3) {
                int rgb = inPixels[i];
                inVert[j + 0] = (float) Math.pow(((rgb >>> 16) & 0xFF) / 255.0, 2.2);
                inVert[j + 1] = (float) Math.pow(((rgb >>> 8) & 0xFF) / 255.0, 2.2);
                inVert[j + 2] = (float) Math.pow(((rgb >>> 0) & 0xFF) / 255.0, 2.2);
            }
            inPixels = null;
            Thread[] thr = new Thread[threads];
            final AtomicInteger sharedY = new AtomicInteger(0);
            final float[] inHorz = new float[inWidth * outHeight * 3];
            final CyclicBarrier barrier = new CyclicBarrier(threads);
            final AtomicInteger sharedX = new AtomicInteger(0);
            final int[] outPixels = new int[outWidth * outHeight];
            for (int i = 0; i < thr.length; i++) {
                thr[i] = new Thread() {

                    public void run() {
                        // Resample vertically and transpose
                        {
                            double sincScale = Math.min((double) outHeight / inHeight, 1);
                            double[] weights = new double[(int) verticalFilterLength + 1];
                            float[] outVertRow = new float[inWidth * 3];
                            while (true) {
                                // For each output row
                                int y = sharedY.getAndIncrement();
                                if (y >= outHeight)
                                    break;
                                double weightSum = 0;
                                // In input image coordinates
                                double centerY = (y + 0.5) / outHeight * inHeight;
                                double filterStartY = centerY - verticalFilterLength / 2;
                                int startIndex = (int) Math.ceil(filterStartY - 0.5);
                                for (int i = 0; i < weights.length; i++) {
                                    int inputY = startIndex + i;
                                    double weight = windowedSinc((inputY + 0.5 - centerY) * sincScale, (inputY + 0.5 - filterStartY) / verticalFilterLength);
                                    weights[i] = weight;
                                    weightSum += weight;
                                }
                                Arrays.fill(outVertRow, 0);
                                for (int i = 0; i < weights.length; i++) {
                                    double weight = weights[i] / weightSum;
                                    int clippedInputY = Math.min(Math.max(startIndex + i, 0), inHeight - 1);
                                    for (int x = 0; x < inWidth; x++) {
                                        // For each pixel in the row
                                        int j = (clippedInputY * inWidth + x) * 3;
                                        outVertRow[x * 3 + 0] += inVert[j + 0] * weight;
                                        outVertRow[x * 3 + 1] += inVert[j + 1] * weight;
                                        outVertRow[x * 3 + 2] += inVert[j + 2] * weight;
                                    }
                                }
                                for (int x = 0; x < inWidth; x++) {
                                    int j = (x * outHeight + y) * 3;
                                    inHorz[j + 0] = outVertRow[x * 3 + 0];
                                    inHorz[j + 1] = outVertRow[x * 3 + 1];
                                    inHorz[j + 2] = outVertRow[x * 3 + 2];
                                }
                            }
                        }
                        // Wait for all threads to finish the phase
                        try {
                            barrier.await();
                        } catch (InterruptedException e) {
                            throw new RuntimeException(e);
                        } catch (BrokenBarrierException e) {
                            throw new RuntimeException(e);
                        }
                        // Resample horizontally and transpose
                        {
                            double sincScale = Math.min((double) outWidth / inWidth, 1);
                            double[] weights = new double[(int) horizontalFilterLength + 1];
                            double[] outHorzCol = new double[outHeight * 3];
                            while (true) {
                                // For each output column
                                int x = sharedX.getAndIncrement();
                                if (x >= outWidth)
                                    break;
                                double weightSum = 0;
                                // In input image coordinates
                                double centerX = (x + 0.5) / outWidth * inWidth;
                                double filterStartX = centerX - horizontalFilterLength / 2;
                                int startIndex = (int) Math.ceil(filterStartX - 0.5);
                                for (int i = 0; i < weights.length; i++) {
                                    int inputX = startIndex + i;
                                    double weight = windowedSinc((inputX + 0.5 - centerX) * sincScale, (inputX + 0.5 - filterStartX) / horizontalFilterLength);
                                    weights[i] = weight;
                                    weightSum += weight;
                                }
                                Arrays.fill(outHorzCol, 0);
                                for (int i = 0; i < weights.length; i++) {
                                    double weight = weights[i] / weightSum;
                                    int clippedInputX = Math.min(Math.max(startIndex + i, 0), inWidth - 1);
                                    for (int y = 0; y < outHeight; y++) {
                                        // For each pixel in the column
                                        int j = (clippedInputX * outHeight + y) * 3;
                                        outHorzCol[y * 3 + 0] += inHorz[j + 0] * weight;
                                        outHorzCol[y * 3 + 1] += inHorz[j + 1] * weight;
                                        outHorzCol[y * 3 + 2] += inHorz[j + 2] * weight;
                                    }
                                }
                                for (int y = 0; y < outHeight; y++) {
                                    // Convert to 8 bits per channel and pack integers
                                    double r = outHorzCol[y * 3 + 0] * 255;
                                    if (r < 0)
                                        r = 0;
                                    if (r > 255)
                                        r = 255;
                                    double g = outHorzCol[y * 3 + 1] * 255;
                                    if (g < 0)
                                        g = 0;
                                    if (g > 255)
                                        g = 255;
                                    double b = outHorzCol[y * 3 + 2] * 255;
                                    if (b < 0)
                                        b = 0;
                                    if (b > 255)
                                        b = 255;
                                    outPixels[y * outWidth + x] = (int) (r + 0.5) << 16 | (int) (g + 0.5) << 8 | (int) (b + 0.5);
                                }
                            }
                        }
                    }
                };
                thr[i].start();
            }
            try {
                for (Thread th : thr) th.join();
            } catch (InterruptedException e) {
                throw new RuntimeException(e);
            }
            outputImage = new BufferedImage(outWidth, outHeight, BufferedImage.TYPE_INT_RGB);
            outputImage.setRGB(0, 0, outWidth, outHeight, outPixels, 0, outWidth);
        }

        // x is measured in half-cycles; y is for the window which has the domain [0, 1]
        private static double windowedSinc(double x, double y) {
            x *= Math.PI;
            double sinc = x != 0 ? Math.sin(x) / x : 1;
            // Triangle window
            double window = 0 <= y && y <= 1 ? 1 - Math.abs(y - 0.5) * 2 : 0;
            return sinc * window;
        }
    }
}


        // Get and check arguments
        if (args.length != 2) {
            System.err.println("Usage: java PreprocessFrames InDir OutDir");
            System.exit(1);
            return;
        }
        File inDir = new File(args[0]);

/*
 * Preprocess frames
 *
 * All rights reserved. Contact Nayuki for licensing.
 * http://example.com
 */
import java.awt.image.BufferedImage;
import java.io.File;
import java.io.IOException;
import java.util.Arrays;
import java.util.concurrent.BrokenBarrierException;
import java.util.concurrent.CyclicBarrier;
import java.util.concurrent.atomic.AtomicInteger;
import javax.imageio.ImageIO;

public final class PreprocessFrames {

    // Configuration
    private static final int INPUT_FRAME_START = 990;

    private static final int INPUT_FRAME_END = 3900;

    private static final int INPUT_FRAME_DECIMATE = 3;

    private static final int IMAGE_WIDTH = 1280;

    private static final int IMAGE_HEIGHT = 192;

    private static final int IMAGE_UPSCALE = 4;

    public static void main(String[] args) throws IOException {

File outDir = new File(args[1]);
        if (!inDir.isDirectory())
            throw new RuntimeException("Invalid input directory");
        if (!outDir.isDirectory())
            throw new RuntimeException("Invalid output directory");
        // Process image frames
        for (int inFrame = INPUT_FRAME_START, outFrame = 0; inFrame < INPUT_FRAME_END; inFrame += INPUT_FRAME_DECIMATE, outFrame++) {
            File inFile = new File(inDir, String.format("%04d.bmp", inFrame));
            File outFile = new File(outDir, String.format("%04d.bmp", outFrame));
            FastSincImageResampler.resampleFile(inFile, IMAGE_WIDTH * IMAGE_UPSCALE, IMAGE_HEIGHT * IMAGE_UPSCALE, outFile);
            System.out.printf("%s --> %s%n", inFile.getName(), outFile.getName());
        }
    }

    // Based on http://example.com
    private static final class FastSincImageResampler {

        /* Convenience methods */
        /**
         * Resamples the specified image file to the specified output dimensions, writing to the specified output file.
         * @param inFile the input image file (must be in BMP or PNG format)
         * @param outWidth the output image width (must be positive)
         * @param outHeight the output image height (must be positive)
         * @param outFile the output image file
         * @throws IOException if an I/O exception occurred in reading the input image file or writing the output image file
         */
        public static void resampleFile(File inFile, int outWidth, int outHeight, File outFile) throws IOException {
            FastSincImageResampler rs = new FastSincImageResampler();
            rs.inputFile = inFile;
            rs.outputWidth = outWidth;
            rs.outputHeight = outHeight;
            rs.outputFile = outFile;
            rs.run();
        }

        /* Full functionality */
        public File inputFile = null;

        public BufferedImage inputImage = null;

        // run() does not read this, but will overwrite this field
        public int inputWidth = -1;

        // run() does not read this, but will overwrite this field
        public int inputHeight = -1;

        // Must set to a positive number
        public int outputWidth = -1;

        // Must set to a positive number
        public int outputHeight = -1;

        // Horizontal filter length (set a positive value, otherwise an automatic default value will be computed)
        public double horizontalFilterLength = -1;

        // Vertical   filter length (set a positive value, otherwise an automatic default value will be computed)
        public double verticalFilterLength = -1;

        // Number of threads to use (set a positive value, otherwise an automatic default value will be computed)
        public int threads = -1;

        // run() does not read this, but will overwrite this field
        public BufferedImage outputImage = null;

        public File outputFile = null;

        // Must be "png", "bmp", or null (auto-detection based on outputFile's extension)
        public String outputFileType = null;

        /**
         * Constructs a blank resampler object - certain fields must be set before calling {@code run()}.
         */
        public FastSincImageResampler() {
        }

        /**
         * Runs the resampler. The data flow operates on a "waterfall" model, reading and writing the fields from top to bottom:
         *

         *
If {@code inputFile} is non-{@code null}, then it is read into {@code inputImage}.

         *
{@code inputImage} needs to be non-{@code null} now (set explicitly or read from {@code inputFile}).

         *
{@code inputImage} is read into {@code inputWidth} and {@code inputHeight}.

         *
{@code outputWidth} and {@code outputHeight} must be positive (set explicitly).

         *
If a filter length is zero or negative, then a default filter length is computed and set for that axis.

         *
If {@code threads} is zero or negative, then a default number of threads is computed and set.

         *
{@code outputImage} is computed by resampling {@code inputImage}. (This is the main purpose of the class.)

         *
If {@code outputFile} is non-{@code null} and {@code outputFileType} is {@code null}: The type is set to {@code "png"} or {@code "bmp"}
         *       if the output file has that extension (case-insensitive), otherwise it is set to {@code "png"} by default.

         *
If {@code outputFile} is non-{@code null}, then {@code outputImage} is written to the file.

         *

         *
After calling {@code run()}, it is recommend that this object should not be reused for another resampling operation.
         * This is because various fields probably need to be cleared, such as the filter length and output file type.

         * @throws IOException if an I/O exception occurred
         * @throws IllegalStateException if there is no input image or the output dimensions are not set to positive values
         */
        public void run() throws IOException {
            // Read input file (optional)
            if (inputFile != null) {
                try {
                    inputImage = ImageIO.read(inputFile);
                } catch (IOException e) {
                    throw new IOException("Error reading input file (" + inputFile + "): " + e.getMessage(), e);
                }
            }
            // Get input image dimensions
            if (inputImage == null)
                throw new IllegalStateException("No input image");
            inputWidth = inputImage.getWidth();
            inputHeight = inputImage.getHeight();
            // Calculate filter lengths (optional)
            if (outputWidth <= 0 || outputHeight <= 0)
                throw new IllegalStateException("Output dimensions not set");
            if (horizontalFilterLength <= 0)
                horizontalFilterLength = Math.max((double) inputWidth / outputWidth, 1) * 4.0;
            if (verticalFilterLength <= 0)
                verticalFilterLength = Math.max((double) inputHeight / outputHeight, 1) * 4.0;
            // Resample the image
            if (threads <= 0)
                threads = Runtime.getRuntime().availableProcessors();
            resampleImage();
            // Write output file (optional)
            if (outputFile != null) {
                if (outputFileType == null) {
                    // Auto-detection by file extension
                    String lowername = outputFile.getName().toLowerCase();
                    if (lowername.endsWith(".bmp"))
                        outputFileType = "bmp";
                    else
                        // Default
                        outputFileType = "png";
                }
                try {
                    ImageIO.write(outputImage, outputFileType, outputFile);
                } catch (IOException e) {
                    throw new IOException("Error writing output file (" + outputFile + "): " + e.getMessage(), e);
                }
            }
        }

        private void resampleImage() {
            final int inWidth = inputWidth;
            final int inHeight = inputHeight;
            final int outWidth = outputWidth;
            final int outHeight = outputHeight;
            // Get packed int pixels
            int[] inPixels = new int[inWidth * inHeight];
            inputImage.getRGB(0, 0, inWidth, inHeight, inPixels, 0, inWidth);
            // Convert to float
            final float[] inVert = new float[inWidth * inHeight * 3];
            for (int i = 0, j = 0; i < inPixels.length; i++, j += 3) {
                int rgb = inPixels[i];
                inVert[j + 0] = (float) Math.pow(((rgb >>> 16) & 0xFF) / 255.0, 2.2);
                inVert[j + 1] = (float) Math.pow(((rgb >>> 8) & 0xFF) / 255.0, 2.2);
                inVert[j + 2] = (float) Math.pow(((rgb >>> 0) & 0xFF) / 255.0, 2.2);
            }
            inPixels = null;
            Thread[] thr = new Thread[threads];
            final AtomicInteger sharedY = new AtomicInteger(0);
            final float[] inHorz = new float[inWidth * outHeight * 3];
            final CyclicBarrier barrier = new CyclicBarrier(threads);
            final AtomicInteger sharedX = new AtomicInteger(0);
            final int[] outPixels = new int[outWidth * outHeight];
            for (int i = 0; i < thr.length; i++) {
                thr[i] = new Thread() {

                    public void run() {
                        // Resample vertically and transpose
                        {
                            double sincScale = Math.min((double) outHeight / inHeight, 1);
                            double[] weights = new double[(int) verticalFilterLength + 1];
                            float[] outVertRow = new float[inWidth * 3];
                            while (true) {
                                // For each output row
                                int y = sharedY.getAndIncrement();
                                if (y >= outHeight)
                                    break;
                                double weightSum = 0;
                                // In input image coordinates
                                double centerY = (y + 0.5) / outHeight * inHeight;
                                double filterStartY = centerY - verticalFilterLength / 2;
                                int startIndex = (int) Math.ceil(filterStartY - 0.5);
                                for (int i = 0; i < weights.length; i++) {
                                    int inputY = startIndex + i;
                                    double weight = windowedSinc((inputY + 0.5 - centerY) * sincScale, (inputY + 0.5 - filterStartY) / verticalFilterLength);
                                    weights[i] = weight;
                                    weightSum += weight;
                                }
                                Arrays.fill(outVertRow, 0);
                                for (int i = 0; i < weights.length; i++) {
                                    double weight = weights[i] / weightSum;
                                    int clippedInputY = Math.min(Math.max(startIndex + i, 0), inHeight - 1);
                                    for (int x = 0; x < inWidth; x++) {
                                        // For each pixel in the row
                                        int j = (clippedInputY * inWidth + x) * 3;
                                        outVertRow[x * 3 + 0] += inVert[j + 0] * weight;
                                        outVertRow[x * 3 + 1] += inVert[j + 1] * weight;
                                        outVertRow[x * 3 + 2] += inVert[j + 2] * weight;
                                    }
                                }
                                for (int x = 0; x < inWidth; x++) {
                                    int j = (x * outHeight + y) * 3;
                                    inHorz[j + 0] = outVertRow[x * 3 + 0];
                                    inHorz[j + 1] = outVertRow[x * 3 + 1];
                                    inHorz[j + 2] = outVertRow[x * 3 + 2];
                                }
                            }
                        }
                        // Wait for all threads to finish the phase
                        try {
                            barrier.await();
                        } catch (InterruptedException e) {
                            throw new RuntimeException(e);
                        } catch (BrokenBarrierException e) {
                            throw new RuntimeException(e);
                        }
                        // Resample horizontally and transpose
                        {
                            double sincScale = Math.min((double) outWidth / inWidth, 1);
                            double[] weights = new double[(int) horizontalFilterLength + 1];
                            double[] outHorzCol = new double[outHeight * 3];
                            while (true) {
                                // For each output column
                                int x = sharedX.getAndIncrement();
                                if (x >= outWidth)
                                    break;
                                double weightSum = 0;
                                // In input image coordinates
                                double centerX = (x + 0.5) / outWidth * inWidth;
                                double filterStartX = centerX - horizontalFilterLength / 2;
                                int startIndex = (int) Math.ceil(filterStartX - 0.5);
                                for (int i = 0; i < weights.length; i++) {
                                    int inputX = startIndex + i;
                                    double weight = windowedSinc((inputX + 0.5 - centerX) * sincScale, (inputX + 0.5 - filterStartX) / horizontalFilterLength);
                                    weights[i] = weight;
                                    weightSum += weight;
                                }
                                Arrays.fill(outHorzCol, 0);
                                for (int i = 0; i < weights.length; i++) {
                                    double weight = weights[i] / weightSum;
                                    int clippedInputX = Math.min(Math.max(startIndex + i, 0), inWidth - 1);
                                    for (int y = 0; y < outHeight; y++) {
                                        // For each pixel in the column
                                        int j = (clippedInputX * outHeight + y) * 3;
                                        outHorzCol[y * 3 + 0] += inHorz[j + 0] * weight;
                                        outHorzCol[y * 3 + 1] += inHorz[j + 1] * weight;
                                        outHorzCol[y * 3 + 2] += inHorz[j + 2] * weight;
                                    }
                                }
                                for (int y = 0; y < outHeight; y++) {
                                    // Convert to 8 bits per channel and pack integers
                                    double r = outHorzCol[y * 3 + 0] * 255;
                                    if (r < 0)
                                        r = 0;
                                    if (r > 255)
                                        r = 255;
                                    double g = outHorzCol[y * 3 + 1] * 255;
                                    if (g < 0)
                                        g = 0;
                                    if (g > 255)
                                        g = 255;
                                    double b = outHorzCol[y * 3 + 2] * 255;
                                    if (b < 0)
                                        b = 0;
                                    if (b > 255)
                                        b = 255;
                                    outPixels[y * outWidth + x] = (int) (r + 0.5) << 16 | (int) (g + 0.5) << 8 | (int) (b + 0.5);
                                }
                            }
                        }
                    }
                };
                thr[i].start();
            }
            try {
                for (Thread th : thr) th.join();
            } catch (InterruptedException e) {
                throw new RuntimeException(e);
            }
            outputImage = new BufferedImage(outWidth, outHeight, BufferedImage.TYPE_INT_RGB);
            outputImage.setRGB(0, 0, outWidth, outHeight, outPixels, 0, outWidth);
        }

        // x is measured in half-cycles; y is for the window which has the domain [0, 1]
        private static double windowedSinc(double x, double y) {
            x *= Math.PI;
            double sinc = x != 0 ? Math.sin(x) / x : 1;
            // Triangle window
            double window = 0 <= y && y <= 1 ? 1 - Math.abs(y - 0.5) * 2 : 0;
            return sinc * window;
        }
    }
}