Esempio n. 1
0
File: Task.cpp Progetto: DikBSD/STE
FilterResultPtr
Task::process(TaskStatus const& status, FilterData const& data)
{
	status.throwIfCancelled();
	
	Dependencies const deps(data.xform().resultingCropArea());
	
	std::auto_ptr<Params> params(m_ptrSettings->getPageParams(m_pageId));
	if (params.get() && !params->dependencies().matches(deps)) {
		params.reset();
	}
	
	OptionsWidget::UiData ui_data;
	ui_data.setSizeCalc(PhysSizeCalc(data.xform()));

	if (params.get()) {
		ui_data.setContentRect(params->contentRect());
		ui_data.setDependencies(params->dependencies());
		ui_data.setMode(params->mode());

		if (params->contentSizeMM().isEmpty() && !params->contentRect().isEmpty()) {
			// Backwards compatibilty: put the missing data where it belongs.
			Params const new_params(
				ui_data.contentRect(), ui_data.contentSizeMM(),
				params->dependencies(), params->mode()
			);
			m_ptrSettings->setPageParams(m_pageId, new_params);
		}
	} else {
		QRectF const content_rect(
			ContentBoxFinder::findContentBox(
				status, data, m_ptrDbg.get()
			)
		);
		ui_data.setContentRect(content_rect);
		ui_data.setDependencies(deps);
		ui_data.setMode(MODE_AUTO);

		Params const new_params(
			ui_data.contentRect(), ui_data.contentSizeMM(), deps, MODE_AUTO
		);
		m_ptrSettings->setPageParams(m_pageId, new_params);
	}
	
	status.throwIfCancelled();
	
	if (m_ptrNextTask) {
		return m_ptrNextTask->process(
			status, FilterData(data, data.xform()),
			ui_data.contentRect()
		);
	} else {
		return FilterResultPtr(
			new UiUpdater(
				m_ptrFilter, m_pageId, m_ptrDbg, data.origImage(),
				data.xform(), ui_data, m_batchProcessing
			)
		);
	}
}
Esempio n. 2
0
File: Task.cpp Progetto: DikBSD/STE
FilterResultPtr
Task::process(TaskStatus const& status, FilterData const& data)
{
	// This function is executed from the worker thread.
	
	status.throwIfCancelled();
	
	ImageTransformation xform(data.xform());
	xform.setPreRotation(m_ptrSettings->getRotationFor(m_imageId));
	
	if (m_ptrNextTask) {
		return m_ptrNextTask->process(status, FilterData(data, xform));
	} else {
		return FilterResultPtr(
			new UiUpdater(
				m_ptrFilter, data.origImage(), m_imageId, xform,
				m_batchProcessing
			)
		);
	}
}
Esempio n. 3
0
void
Despeckle::despeckleInPlace(
	BinaryImage& image, Dpi const& dpi, Level const level,
	TaskStatus const& status, DebugImages* const dbg)
{
	Settings const settings(Settings::get(level, dpi));

	ConnectivityMap cmap(image, CONN8);
	if (cmap.maxLabel() == 0) {
		// Completely white image?
		return;
	}

	status.throwIfCancelled();
	
	std::vector<Component> components(cmap.maxLabel() + 1);
	std::vector<BoundingBox> bounding_boxes(cmap.maxLabel() + 1);

	int const width = image.width();
	int const height = image.height();

	uint32_t* const cmap_data = cmap.data();
	
	// Count the number of pixels and a bounding rect of each component.
	uint32_t* cmap_line = cmap_data;
	int const cmap_stride = cmap.stride();
	for (int y = 0; y < height; ++y) {
		for (int x = 0; x < width; ++x) {
			uint32_t const label = cmap_line[x];
			++components[label].num_pixels;
			bounding_boxes[label].extend(x, y);
		}
		cmap_line += cmap_stride;
	}
	
	status.throwIfCancelled();

	// Unify big components into one.
	std::vector<uint32_t> remapping_table(components.size());
	uint32_t unified_big_component = 0;
	uint32_t next_avail_component = 1;
	for (uint32_t label = 1; label <= cmap.maxLabel(); ++label) {
		if (bounding_boxes[label].width() < settings.bigObjectThreshold &&
				bounding_boxes[label].height() < settings.bigObjectThreshold) {
			components[next_avail_component] = components[label];
			remapping_table[label] = next_avail_component;
			++next_avail_component;
		} else {
			if (unified_big_component == 0) {
				unified_big_component = next_avail_component;
				++next_avail_component;
				components[unified_big_component] = components[label];
				// Set num_pixels to a large value so that canBeAttachedTo()
				// always allows attaching to any such component.
				components[unified_big_component].num_pixels = width * height;
			}
			remapping_table[label] = unified_big_component;
		}
	}
	components.resize(next_avail_component);
	std::vector<BoundingBox>().swap(bounding_boxes); // We don't need them any more.
	
	status.throwIfCancelled();

	uint32_t const max_label = next_avail_component - 1;
	
	// Remapping individual pixels.
	cmap_line = cmap_data;
	for (int y = 0; y < height; ++y) {
		for (int x = 0; x < width; ++x) {
			cmap_line[x] = remapping_table[cmap_line[x]];
		}
		cmap_line += cmap_stride;
	}
	if (dbg) {
		dbg->add(cmap.visualized(), "big_components_unified");
	}

	status.throwIfCancelled();
	
	// Build a Voronoi diagram.
	std::vector<Distance> distance_matrix;
	voronoi(cmap, distance_matrix);
	if (dbg) {
		dbg->add(cmap.visualized(), "voronoi");
	}
	
	status.throwIfCancelled();

	Distance* const distance_data = &distance_matrix[0] + width + 3;
	
	// Now build a bidirectional map of distances between neighboring
	// connected components.
	
	typedef std::map<Connection, uint32_t> Connections; // conn -> sqdist
	Connections conns;
	
	voronoiDistances(cmap, distance_matrix, conns);
	
	status.throwIfCancelled();

	// Tag connected components with ANCHORED_TO_BIG or ANCHORED_TO_SMALL.
	BOOST_FOREACH(Connections::value_type const& pair, conns) {
		Connection const conn(pair.first);
		uint32_t const sqdist = pair.second;
		Component& comp1 = components[conn.lesser_label];
		Component& comp2 = components[conn.greater_label];
		tagSourceComponent(comp1, comp2, sqdist, settings);
		tagSourceComponent(comp2, comp1, sqdist, settings);
	}
Esempio n. 4
0
FilterResultPtr
Task::process(
	TaskStatus const& status, FilterData const& data,
	QPolygonF const& content_rect_phys)
{
	status.throwIfCancelled();

	Params params(m_ptrSettings->getParams(m_pageId));
	RenderParams const render_params(params.colorParams());
	QString const out_file_path(m_outFileNameGen.filePathFor(m_pageId));
	QFileInfo const out_file_info(out_file_path);

	ImageTransformation new_xform(data.xform());
	new_xform.postScaleToDpi(params.outputDpi());

	QString const automask_dir(Utils::automaskDir(m_outFileNameGen.outDir()));
	QString const automask_file_path(
		QDir(automask_dir).absoluteFilePath(out_file_info.fileName())
	);
	QFileInfo automask_file_info(automask_file_path);

	QString const speckles_dir(Utils::specklesDir(m_outFileNameGen.outDir()));
	QString const speckles_file_path(
		QDir(speckles_dir).absoluteFilePath(out_file_info.fileName())
	);
	QFileInfo speckles_file_info(speckles_file_path);

	bool const need_picture_editor = render_params.mixedOutput() && !m_batchProcessing;
	bool const need_speckles_image = params.despeckleLevel() != DESPECKLE_OFF
		&& params.colorParams().colorMode() != ColorParams::COLOR_GRAYSCALE && !m_batchProcessing;
	
	OutputGenerator const generator(
		params.outputDpi(), params.colorParams(), params.despeckleLevel(),
		new_xform, content_rect_phys
	);
	
	OutputImageParams new_output_image_params(
		generator.outputImageSize(), generator.outputContentRect(),
		new_xform, params.outputDpi(), params.colorParams(),
		params.dewarpingMode(), params.distortionModel(),
		params.depthPerception(), params.despeckleLevel() 
	);

	ZoneSet const new_picture_zones(m_ptrSettings->pictureZonesForPage(m_pageId));
	ZoneSet const new_fill_zones(m_ptrSettings->fillZonesForPage(m_pageId));
	
	bool need_reprocess = false;
	do { // Just to be able to break from it.
		
		std::auto_ptr<OutputParams> stored_output_params(
			m_ptrSettings->getOutputParams(m_pageId)
		);
		
		if (!stored_output_params.get()) {
			need_reprocess = true;
			break;
		}
		
		if (!stored_output_params->outputImageParams().matches(new_output_image_params)) {
			need_reprocess = true;
			break;
		}

		if (!PictureZoneComparator::equal(stored_output_params->pictureZones(), new_picture_zones)) {
			need_reprocess = true;
			break;
		}

		if (!FillZoneComparator::equal(stored_output_params->fillZones(), new_fill_zones)) {
			need_reprocess = true;
			break;
		}
		
		if (!out_file_info.exists()) {
			need_reprocess = true;
			break;
		}
		
		if (!stored_output_params->outputFileParams().matches(OutputFileParams(out_file_info))) {
			need_reprocess = true;
			break;
		}

		if (need_picture_editor) {
			if (!automask_file_info.exists()) {
				need_reprocess = true;
				break;
			}

			if (!stored_output_params->automaskFileParams().matches(OutputFileParams(automask_file_info))) {
				need_reprocess = true;
				break;
			}
		}

		if (need_speckles_image) {
			if (!speckles_file_info.exists()) {
				need_reprocess = true;
				break;
			}
			if (!stored_output_params->specklesFileParams().matches(OutputFileParams(speckles_file_info))) {
				need_reprocess = true;
				break;
			}
		}
	
	} while (false);
	
	QImage out_img;
	BinaryImage automask_img;
	BinaryImage speckles_img;
	
	if (!need_reprocess) {
		QFile out_file(out_file_path);
		if (out_file.open(QIODevice::ReadOnly)) {
			out_img = ImageLoader::load(out_file, 0);
		}
		need_reprocess = out_img.isNull();

		if (need_picture_editor && !need_reprocess) {
			QFile automask_file(automask_file_path);
			if (automask_file.open(QIODevice::ReadOnly)) {
				automask_img = BinaryImage(ImageLoader::load(automask_file, 0));
			}
			need_reprocess = automask_img.isNull() || automask_img.size() != out_img.size();
		}

		if (need_speckles_image && !need_reprocess) {
			QFile speckles_file(speckles_file_path);
			if (speckles_file.open(QIODevice::ReadOnly)) {
				speckles_img = BinaryImage(ImageLoader::load(speckles_file, 0));
			}
			need_reprocess = speckles_img.isNull();
		}
	}

	if (need_reprocess) {
		// Even in batch processing mode we should still write automask, because it
		// will be needed when we view the results back in interactive mode.
		// The same applies even more to speckles file, as we need it not only
		// for visualization purposes, but also for re-doing despeckling at
		// different levels without going through the whole output generation process.
		bool const write_automask = render_params.mixedOutput();
		bool const write_speckles_file = params.despeckleLevel() != DESPECKLE_OFF &&
			params.colorParams().colorMode() != ColorParams::COLOR_GRAYSCALE; 

		automask_img = BinaryImage();
		speckles_img = BinaryImage();

		DistortionModel distortion_model;
		if (params.dewarpingMode() == DewarpingMode::MANUAL) {
			distortion_model = params.distortionModel();
		}
		// OutputGenerator will write a new distortion model
		// there, if dewarping mode is AUTO.

		out_img = generator.process(
			status, data, new_picture_zones, new_fill_zones,
			params.dewarpingMode(), distortion_model,
			params.depthPerception(),
			write_automask ? &automask_img : 0,
			write_speckles_file ? &speckles_img : 0,
			m_ptrDbg.get()
		);

		if (params.dewarpingMode() == DewarpingMode::AUTO && distortion_model.isValid()) {
			// A new distortion model was generated.
			// We need to save it to be able to modify it manually.
			params.setDistortionModel(distortion_model);
			m_ptrSettings->setParams(m_pageId, params);
			new_output_image_params.setDistortionModel(distortion_model);
		}

		if (write_speckles_file && speckles_img.isNull()) {
			// Even if despeckling didn't actually take place, we still need
			// to write an empty speckles file.  Making it a special case
			// is simply not worth it.
			BinaryImage(out_img.size(), WHITE).swap(speckles_img);
		}

		bool invalidate_params = false;
		
		if (!TiffWriter::writeImage(out_file_path, out_img)) {
			invalidate_params = true;
		} else {
			deleteMutuallyExclusiveOutputFiles();
		}

		if (write_automask) {
			// Note that QDir::mkdir() will fail if the parent directory,
			// that is $OUT/cache doesn't exist. We want that behaviour,
			// as otherwise when loading a project from a different machine,
			// a whole bunch of bogus directories would be created.
			QDir().mkdir(automask_dir);
			// Also note that QDir::mkdir() will fail if the directory already exists,
			// so we ignore its return value here.

			if (!TiffWriter::writeImage(automask_file_path, automask_img.toQImage())) {
				invalidate_params = true;
			}
		}
		if (write_speckles_file) {
			if (!QDir().mkpath(speckles_dir)) {
				invalidate_params = true;
			} else if (!TiffWriter::writeImage(speckles_file_path, speckles_img.toQImage())) {
				invalidate_params = true;
			}
		}

		if (invalidate_params) {
			m_ptrSettings->removeOutputParams(m_pageId);
		} else {
			// Note that we can't reuse *_file_info objects
			// as we've just overwritten those files.
			OutputParams const out_params(
				new_output_image_params,
				OutputFileParams(QFileInfo(out_file_path)),
				write_automask ? OutputFileParams(QFileInfo(automask_file_path))
				: OutputFileParams(),
				write_speckles_file ? OutputFileParams(QFileInfo(speckles_file_path))
				: OutputFileParams(),
				new_picture_zones, new_fill_zones
			);

			m_ptrSettings->setOutputParams(m_pageId, out_params);
		}
		
		m_ptrThumbnailCache->recreateThumbnail(ImageId(out_file_path), out_img);
	}

	DespeckleState const despeckle_state(
		out_img, speckles_img, params.despeckleLevel(), params.outputDpi()
	);

	DespeckleVisualization despeckle_visualization;
	if (m_lastTab == TAB_DESPECKLING) {
		// Because constructing DespeckleVisualization takes a noticeable
		// amount of time, we only do it if we are sure we'll need it.
		// Otherwise it will get constructed on demand.
		despeckle_visualization = despeckle_state.visualize();
	}

	if (CommandLine::get().isGui()) {
		return FilterResultPtr(
			new UiUpdater(
				m_ptrFilter, m_ptrSettings, m_ptrDbg, params,
				new_xform, generator.outputContentRect(),
				m_pageId, data.origImage(), out_img, automask_img,
				despeckle_state, despeckle_visualization,
				m_batchProcessing, m_debug
			)
		);
	} else {
		return FilterResultPtr(0);
	}
}